Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET cancelling async I/O operations

In this particular case, I'm writing a TCP/IP server, and I'm using TcpListener.BeginAcceptTcpClient() to accept incoming connections. The server logics is implemented in a single class implementing the IDisposable interface: when Dispose() is called, I want to cancel the BeginAcceptTcpClient() operation.

Now in more general terms: when using the async I/O model of .NET (Begin* and End*), how can I cancel the operations?

My idea would be something like this:

private volatile bool canceledFlag = false;

this.listener.BeginAcceptTcpClient(asyncResult =>
{
    if(this.canceledFlag) return;

    // ...
}, null);

Dispose() would set the flag to true and would also call this.listener.Stop(). However I recall having read that for every Begin* call there must be a matching End* call or bad things would happen.

So how should I do this then? Please note that I'm looking for a general solution for cancellation with Begin* and End* methods - I just gave you my concrete usage scenario to help you understand my question.

like image 203
ShdNx Avatar asked Jan 19 '23 18:01

ShdNx


2 Answers

The general solution is to call Close() or Dispose() on whatever object whose BeginXxxx() method you called. That will cause the callback to run, when you call EndXxxx() then you'll get an ObjectDisposedException. Which you should catch and treat as a 'end of use' signal, clean up and exit the callback right away. While frowned upon, the use of an exception for flow control is unavoidable.

For TcpListener, use Server.Dispose(), a bit more efficient than calling Stop().

like image 151
Hans Passant Avatar answered Jan 29 '23 03:01

Hans Passant


The AsyncCallback is called when an async process completes. It allows the async thread to rejoin the original, and any exceptions can then bubble up (rather than being lost in the background). It also allows you to capture any return values.

Typically you would use Begin / End pattern as follows:

  Action action = () = > DoSomething();
  action.BeginInvoke(action.EndInvoke, null);

Or

 Action action = () = > DoSomething();
 action.BeginInvoke(OnComplete, null);

 private void OnComplete(IAsyncResult ar)
 {
     AsyncResult result = (AsyncResult)ar;
     var caller = (Action)result.AsyncDelegate;
     caller.EndInvoke();

     // do something else when completed
 }

It's up to specific implementations to provide a way to cancel an async process before completion. Usually this is done with a CancelAsync implementation that internally stops the task early. In the case of TcpListener you can just call listener.Server.Dispose();

 TcpListener listener = new TcpListener(port);
 IAsyncResult = listener.BeginAcceptTcpClient(listener.EndAcceptTcpClient, null);

 // on dispose
 listener.Server.Dispose();
like image 38
TheCodeKing Avatar answered Jan 29 '23 04:01

TheCodeKing