TcpListener: как прекратить прослушивание при ожидании AcceptTcpClientAsync()
Я не знаю, как правильно закрыть TcpListener, пока асинхронный метод ожидает входящих соединений.
Я нашел этот код на SO, вот код:
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
Хотя существует довольно сложное решение на основе блога Стивена Тууба, существует гораздо более простое решение с использованием встроенных .NET API:
var cancellation = new CancellationTokenSource();
await Task.Run(() => listener.AcceptTcpClientAsync(), cancellation.Token);
// somewhere in another thread
cancellation.Cancel();
Это решение не будет уничтожать ожидающий вызов приема. Но другие решения тоже этого не делают, и это решение хотя бы короче.
Обновление:. Более полный пример, показывающий, что должно произойти после того, как будет сообщено об аннулировании:
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();
Ответ 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();
Это может быть взлом, но он кажется более красивым, чем другие варианты:)
Ответ 3
Поскольку здесь нет надлежащего рабочего примера, вот один из них:
Предполагая, что у вас есть в области 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;
}
}
Ответ 4
Вызов StopListening
(который размещает сокет) правильный. Просто проглотите эту конкретную ошибку. Вы не можете этого избежать, так как вам все равно нужно остановить ожидающий вызов. Если вы не протекаете сокет и ожидающий async IO, и порт остается в использовании.
Ответ 5
Определите этот метод расширения:
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());
Ответ 6
Я использовал следующее решение при постоянном прослушивании новых подключающихся клиентов:
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
, поэтому извините меня, если возникнет проблема с этим подходом - я был бы рад видеть, что он указал, чтобы учиться на нем!