问题
Given the following code, modified from Stephen Toub's article. http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx
public async Task Start(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await this.acceptCount.WaitAsync(token).ConfigureAwait(false);
if (token.IsCancellationRequested)
break;
var args = new SocketAsyncEventArgs();
args.UserToken = new SocketFrame
{
CancellationToken = token,
Buffer = null,
Message = null,
};
// How do I manage this additional task?
args.Completed += (s, e) => this.AcceptInbound((Socket)s, e).Wait(token);
if (!this.socket.AcceptAsync(args))
await this.AcceptInbound(this.socket, args);
}
}
private async Task AcceptInbound(Socket sender, SocketAsyncEventArgs e)
{
var frame = (SocketFrame)e.UserToken;
this.acceptCount.Release();
Socket connectedClient = e.AcceptSocket;
var args = new SocketAsyncEventArgs();
args.SetBuffer(new byte[0x1000], 0, 0x1000);
var awaitable = new SocketAwaitable(args);
while (!frame.CancellationToken.IsCancellationRequested)
{
await connectedClient.ReceiveAsync(awaitable);
var bytesRead = args.BytesTransferred;
if (bytesRead <= 0)
break;
if (this.AppendByteArrayToFrame(frame, args.Buffer, bytesRead))
this.reader.MessageReceived(frame.Message);
}
}
How do I avoid the Wait
on the args.Completed
event?
I want exceptions raised in AcceptInbound to bubble up, so I really don't want to wait there.
What I am trying to achieve is to bind the AcceptInbound task to the current task, so that when I wait on the current task, the exceptions raised are caught.
回答1:
You can register an async
event handler (which is the only place async void
is appropriate). That allows you to await
instead of blocking with Wait
:
args.Completed += async (s, e) => await AcceptInbound((Socket)s, e);
If instead you want Start
to handle completion and exceptions from all these tasks then I would store them all until Start
completes and then use Task.WhenAll
to make sure all operations completed and rethrow any exception they may produced:
public async Task Start(CancellationToken token)
{
var tasks = new ConcurrentBag<Task>();
while (!token.IsCancellationRequested)
{
// ...
args.Completed += (s, e) => tasks.Add(AcceptInbound((Socket)s, e));
// ...
}
await Task.WhenAll(tasks);
}
回答2:
TaskCompletionSource to the rescue!
Take a look at this:
private TaskCompletionSource<Something> _tcs = new TaskCompletionSource<Something>();
public void FinishAwait(Something result) {
_tcs.SetResult(result);
}
public void FailAwait(Exception exception) {
_tcs.SetException(exception);
}
public async Task<Something> M() {
var result = await _tcs.Task;
return result;
}
This a very customised way of controlling when and how a particular Task
completes.
You could use that to await
a custom built task which is manually triggered by the completion of the AcceptAsync
operation's event handler.
In your case, the generic argument T
can be object
because we only needed it to be void
and object
is the most benign of all types.
You could then await that intermediate task inside the Start
method, just before awaiting the AcceptInbound
method's Task
.
Your problem was not that Exceptions are not passed from
callee to caller via await call but rather that you were
calling AcceptIncomming
too soon.
Simply receiving true
in the call to AcceptAsync
doesn't mean that the "Accept occurred" but rather that is successfully started.
Take a look at that part of the code:
public async Task Start(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await this.acceptCount.WaitAsync(token).ConfigureAwait(false);
if (token.IsCancellationRequested)
break;
var args = new SocketAsyncEventArgs();
args.UserToken = new SocketFrame
{
CancellationToken = token,
Buffer = null,
Message = null,
};
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// How do I manage this additional task?
args.Completed += (s, e) => {
switch (e.SocketError) {
case SocketError.Success:
tcs.SetResult(null);
break;
default:
tcs.SetException(new SocketException());
break;
}
};
if (!this.socket.AcceptAsync(args)) {
// receiving a true value here
// only means that AcceptAsync
// started successfully
// we still need to
// "await for the Completed event handler"
await tcs.Task; // this will automatically throw
// if tcs.SetException(...) was used
// also, it will return null if all is well
// but most importantly, it will allow us to
// know when to call the next method
await this.AcceptInbound(this.socket, args);
}
}
}
来源:https://stackoverflow.com/questions/29116081/awaiting-socket-operations