.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.
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().
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();
The alternative I would recommend is not using the async-Begin-End pattern but Tasks.
You can use one of the TaskFactory.FromAsync overloads to create a task from your Begin/End methods and use this. You can use this .ContinueWith overload to specify a continuation with your own CancellationToken to stop it. IMHO it's recommended to use Tasks whenever you can because the next version of C# will build upon Task with it's async/await support.
Here is a nice article explaining the common patterns and Tasks
REMARK:
In my original answer I said you can use .Dispose to kill the Task but this might cause serious problems, because you can only Dispose Tasks in the Completed state.
精彩评论