2013-10-07 4 views
10

Я не знаю, как правильно закрыть TcpListener, когда асинхронный метод ожидает входящих соединений. Я нашел этот код на SO, здесь код:TcpListener: как прекратить прослушивание при ожидании AcceptTcpClientAsync()

public class Server 
{ 
    private TcpListener _Server; 
    private bool _Active; 

    public Server() 
    { 
     _Server = new TcpListener(IPAddress.Any, 5555); 
    } 

    public async void StartListening() 
    { 
     _Active = true; 
     _Server.Start(); 
     await AcceptConnections(); 
    } 

    public void StopListening() 
    { 
     _Active = false; 
     _Server.Stop(); 
    } 

    private async Task AcceptConnections() 
    { 
     while (_Active) 
     { 
      var client = await _Server.AcceptTcpClientAsync(); 
      DoStuffWithClient(client); 
     } 
    } 

    private void DoStuffWithClient(TcpClient client) 
    { 
     // ... 
    } 

} 

И главная:

static void Main(string[] args) 
    { 
     var server = new Server(); 
     server.StartListening(); 

     Thread.Sleep(5000); 

     server.StopListening(); 
     Console.Read(); 
    } 

Исключение бросили на этой линии

 await AcceptConnections(); 

, когда я называю Server.StopListening(), объект удаляется.

Так что мой вопрос в том, как я могу отменить AcceptTcpClientAsync() для правильного закрытия TcpListener.

+1

нашел ответ на SO: [http://stackoverflow.com/questions/14524209/what-is-the-correct-way-to-cancel-an-async-operation-that-doesnt-accept-a- Cance] [1] [1]: http://stackoverflow.com/questions/14524209/what-is-the-correct-way-to-cancel-an-async-operation-that-doesnt -accept-a-cance Thanks – Baptiste

+0

Почему вы не используете try {}, чтобы поймать исключение? –

ответ

2

Хотя существует довольно complicated solution на основе blog post by Stephen Toub, есть гораздо более простое решение с помощью встроенных .NET API:

var cancellation = new CancellationTokenSource(); 
await Task.Run(() => listener.AcceptTcpClientAsync(), cancellation.Token); 

// somewhere in another thread 
cancellation.Cancel(); 

Это решение не будет убивать ожидающий принять вызов. Но другие решения тоже этого не делают, и это решение хотя бы короче.

Update: Более полный пример, который показывает, что должно произойти после того, как отмена сигнализируется:

var cancellation = new CancellationTokenSource(); 
var listener = new TcpListener(IPAddress.Any, 5555); 
listener.Start(); 
try 
{ 
    while (true) 
    { 
     var client = await Task.Run(
      () => listener.AcceptTcpClientAsync(), 
      cancellation.Token); 
     // use the client, pass CancellationToken to other blocking methods too 
    } 
} 
finally 
{ 
    listener.Stop(); 
} 

// somewhere in another thread 
cancellation.Cancel(); 
+3

Это утечка сокета, асинхронный вызов и оставляет порт в использовании навсегда. – usr

+0

@usr Предполагается, что цикл принятия впоследствии выходит, а некоторые, наконец, блокируют все, вызывая TcpListener.Stop(). –

+0

Нет необходимости в этом. Просто позвоните в Stop, и вся деятельность прекратится. Как вы сказали, Стоун должен быть вызван в любом случае рано или поздно. – usr

2

Работал для меня: Создать локальный фиктивный клиент для подключения к слушателю, а после подключения принимается, просто не выполняйте другой асинхронный прием (используйте активный флаг).

// This is so the accept callback knows to not 
_Active = false; 

TcpClient dummyClient = new TcpClient(); 
dummyClient.Connect(m_listener.LocalEndpoint as IPEndPoint); 
dummyClient.Close(); 

Это может быть хак, но мне кажется красивее, чем другие варианты здесь :)

+0

Это забавный хак. Не работает, если слушатель не прослушивает интерфейс loopback. – usr

0

Вызов StopListening (который располагает сокет) правильно. Просто проглотите эту конкретную ошибку. Вы не можете этого избежать, так как вам все равно нужно остановить ожидающий вызов. Если вы не пропустите разъем и ожидающий асинхронный ввод-вывод, и порт остается в использовании.

0

Определить этот метод расширения:

public static class Extensions 
{ 
    public static async Task<TcpClient> AcceptTcpClientAsync(this TcpListener listener, CancellationToken token) 
    { 
     try 
     { 
      return await listener.AcceptTcpClientAsync(); 
     } 
     catch (Exception ex) when (token.IsCancellationRequested) 
     { 
      throw new OperationCanceledException("Cancellation was requested while awaiting TCP client connection.", ex); 
     } 
    } 
} 

Перед использованием методы расширения принимать клиентские подключения, выполните следующие действия:

token.Register(() => listener.Stop()); 
1

Поскольку нет надлежащего рабочего примера здесь, здесь один:

Предполагая, что у вас есть в области как cancellationToken, так и tcpListener, то вы можете сделать следующее:

using (cancellationToken.Register(() => tcpListener.Stop())) 
{ 
    try 
    { 
     var tcpClient = await tcpListener.AcceptTcpClientAsync(); 
     // … carry on … 
    } 
    catch (InvalidOperationException) 
    { 
     // Either tcpListener.Start wasn't called (a bug!) 
     // or the CancellationToken was cancelled before 
     // we started accepting (giving an InvalidOperationException), 
     // or the CancellationToken was cancelled after 
     // we started accepting (giving an ObjectDisposedException). 
     // 
     // In the latter two cases we should surface the cancellation 
     // exception, or otherwise rethrow the original exception. 
     cancellationToken.ThrowIfCancellationRequested(); 
     throw; 
    } 
} 
0

я использовал следующее решение, когда постоянно прослушивание новых клиентов, соединяющие:

public async Task ListenAsync(IPEndPoint endPoint, CancellationToken cancellationToken) 
{ 
    TcpListener listener = new TcpListener(endPoint); 
    listener.Start(); 

    // Stop() typically makes AcceptSocketAsync() throw an ObjectDisposedException. 
    cancellationToken.Register(() => listener.Stop()); 

    // Continually listen for new clients connecting. 
    try 
    { 
     while (true) 
     { 
      cancellationToken.ThrowIfCancellationRequested(); 
      Socket clientSocket = await listener.AcceptSocketAsync(); 
     } 
    } 
    catch (OperationCanceledException) { throw; } 
    catch (Exception) { cancellationToken.ThrowIfCancellationRequested(); } 
} 
  • зарегистрировать обратный вызов для вызова Stop() на экземпляре TcpListener когда CancellationToken будет отменен.
  • AcceptSocketAsync обычно сразу же выбрасывает ObjectDisposedException.
  • Я поймаю любой Exception, кроме OperationCanceledException, хотя для вызова «здорового» OperationCanceledException внешнему абоненту.

Я довольно новый для async программирования, так что простите меня, если есть проблема с этим подходом, - я был бы рад видеть, что указали, чтобы узнать от него!

Смежные вопросы