2013-10-07 9 views
10

Non so come chiudere correttamente un TcpListener mentre un metodo asincrono attende le connessioni in entrata. Ho trovato questo codice su SO, qui il codice:TcpListener: come interrompere l'ascolto durante l'attesa 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) 
    { 
     // ... 
    } 

} 

e le principali:

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

     Thread.Sleep(5000); 

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

Un'eccezione è gettato su questa linea

 await AcceptConnections(); 

quando chiamo Server.StopListening(), l'oggetto è cancellato.

Quindi la mia domanda è, come posso annullare AcceptTcpClientAsync() per chiudere correttamente TcpListener.

+1

ha trovato una risposta su SO: [http://stackoverflow.com/questions/14524209/what-is-the-correct-way-to-cancel-an-async-operation-that-doesnt-accept-a- cato] [1] [1]: http://stackoverflow.com/questions/14524209/what-is-the-correct-way-to-cancel-an-async-operation-that-doesnt -accept-a-cance Grazie – Baptiste

+0

Perché non si usa un try {} per catturare l'eccezione? –

risposta

2

Mentre non v'è abbastanza complicated solution sulla base di un blog post by Stephen Toub, non c'è soluzione molto più semplice utilizzando le API incorporate .NET:

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

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

Questa soluzione non ucciderà la accetterà chiamata in attesa. Ma le altre soluzioni non lo fanno e questa soluzione è almeno più breve.

Aggiornamento: Un esempio più completo che mostra quello che dovrebbe accadere dopo la cancellazione viene segnalata:

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

Questo perde il socket, la chiamata asincrona e lascia la porta in uso per sempre. – usr

+0

@usr Si presume che il ciclo accept successivamente esca e che alcuni blocchi finalmente puliscano tutto chiamando TcpListener.Stop(). –

+0

Non ce n'è bisogno. Basta chiamare Stop e tutte le attività cesseranno. Come hai detto, Stop deve essere chiamato comunque prima o poi. – usr

2

ha funzionato per me: creare un client fittizio locale per connettersi a chi ascolta, e dopo la connessione viene accettato solo non fare un altro async accept (usa il flag attivo).

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

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

questo potrebbe essere un hack, ma sembra più bella di altre opzioni qui :)

+0

Questo è un divertente trucco. Non funziona se il listener non ascolta sull'interfaccia loopback. – usr

0

Calling StopListening (che dispone la presa) è corretta. Inghiotti quel particolare errore. Non puoi evitarlo dato che in qualche modo devi comunque interrompere la chiamata in attesa. In caso contrario, si perde il socket e l'I/O asincrono in sospeso e la porta rimane in uso.

0

Definire questo metodo di estensione:

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); 
     } 
    } 
} 

Prima di utilizzare il metodo di estensione di accettare connessioni client, fare questo:

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

In mancanza di un esempio di lavoro corretta qui, qui è uno:

Supponendo di avere in ambito sia cancellationToken e tcpListener, quindi è possibile effettuare le seguenti operazioni:

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

ho usato la seguente soluzione, quando continuamente l'ascolto per i nuovi clienti di collegamento:

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(); } 
} 
  • registro un callback per chiamare Stop() sull'istanza TcpListener quando il CancellationToken viene cancellato.
  • AcceptSocketAsync in genere genera immediatamente un ObjectDisposedException.
  • Prendo uno qualsiasi Exception diverso da OperationCanceledException per lanciare un "sane" OperationCanceledException al chiamante esterno.

Sono abbastanza nuovo per async programmazione, così mi scusi se c'è un problema con questo approccio - sarei felice di vederlo sottolineato di imparare da esso!

Problemi correlati