Awaiting Socket Operations

狂风中的少年 提交于 2021-02-08 06:43:28

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!