C# Sockets: synchronous calls within asynchronous

2019-06-01 18:48发布

In MSDN example of using asynchronous sockets, the receiving data in socket is done by repeatedly calling asynchronous BeginReceive from the callback handler which is called by BeginReceive:

private static void ReceiveCallback( IAsyncResult ar ) {
//...Skipped...
        if (bytesRead > 0) {
            // There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
                //  Get the rest of the data.
            client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
                new AsyncCallback(ReceiveCallback), state);
        } else {
// ...Skipped...
}

http://msdn.microsoft.com/en-us/library/bbx2eya8(v=vs.110).aspx

Is there a necessity to make again an asynchronous call from a handler which is already executing in a separate thread? Can one simply use Receive in a loop in this handler? Something like:

while (bytesRead) {

bytesRead = client.Receive(state.buffer, 0, client.Available, 
                                   SocketFlags.None);
// Etc...
}

1条回答
▲ chillily
2楼-- · 2019-06-01 19:22

The major goal of the APM pattern was to avoid blocking the calling thread while waiting for the result of asynchronous operation, and thus boost the scalability of the server applications.

If in your AsyncCallback you continue calling Receive in the loop synchronously, you'll still be blocking the IOCP thread on which the initial BeginReceive has completed. This may be OK for a client-side UI app, where you might not care about ThreadPool starvation, but this is certainly not a good idea for a server-side app, where the blocked thread could otherwise be serving other incoming client requests.

Note that with C# 5.0 / .NET 4.5 and on, APM is considered legacy. You can use async/await and new Task-based Asynchronous Pattern (TAP) pattern, which greatly simplifies the asynchronous code development, e.g.:

async Task<string> ReadAllAsync()
{
    var sb = new StringBuffer();

    using (var tcp = new TcpClient())
    {
        await tcp.ConnectAsync(IPAddress.Parse("localhost"), 8080).ConfigureAwait(false);
        var buffer = new byte[1024];
        using (var stream = tcp.GetStream())
        {
            var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
            if (0 == bytesRead)
                break;
            sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
        }
    }

    return sb.ToString();
}

If for some reason you don't want to use NetworkStream.ReadAsync, you can wrap APM-style socket APIs as TAP with Task.FromAsync:

public static class SocketsExt
{
    static public Task ReceiveDataAsync(
        this TcpClient tcpClient,
        byte[] buffer)
    {
        return Task.Factory.FromAsync(
            (asyncCallback, state) =>
                tcpClient.Client.BeginReceive(buffer, 0, buffer.Length, 
                    SocketFlags.None, asyncCallback, state),
            (asyncResult) =>
                tcpClient.Client.EndReceive(asyncResult), 
            null);
    }

    static public async Task<Int32> ReceiveInt32Async(
        this TcpClient tcpClient)
    {
        var data = new byte[sizeof(Int32)];
        await tcpClient.ReceiveDataAsync(data).ConfigureAwait(false);
        return BitConverter.ToInt32(data, 0);
    }
}
查看更多
登录 后发表回答