多线程异步模式

时间:2015-01-27 15:31:37

标签: c# multithreading asynchronous task-parallel-library async-await

我有一个多线程通过单个套接字发送数据的场景。已在消息中插入唯一ID,并在响应消息中回显唯一ID。当套接字被隔离到单个客户端时(正如预期的那样),一切都很有效。现在,我正在为多个线程寻找异步/等待模式,客户端等待特定的响应。

要演示的一些代码:

using (var ns = new NetworkStream(_socket))
{
    byte[] requestBuffer = GetBuffer(request);
    await ns.WriteAsync(requestBuffer, 0, request.Length);
    byte[] responseBuffer = await ReceiveMessageAsync(ns);

    // process response message
}

上面的示例在多线程场景中不起作用,因为消息可以以任何顺序返回,因此线路上的下一条消息可能不属于当前客户端。我的想法是客户端将使用其唯一ID(将其存储在字典中)来注册委托或任务,并且当带有该唯一ID的消息返回时,委托或任务将被完成'响应字节。我猜这个使用EventHandler很容易实现,但我正在寻找一种等待响应的方法。

例如:

using (var ns = new CustomNetworkStream(_socket))
{
    Task waitTask = ns.RegisterTask(this.UniqueId);

    byte[] requestBuffer = GetBuffer(request);
    await ns.WriteAsync(requestBuffer, 0, request.Length);

    byte[] responseBuffer = await waitTask;

    // process response message
}

我不知道" RegisterTask"方法看起来如何,如何存储任务,如何使任务等待'然后完成'以任务为结果。

有什么想法吗?我已经研究过Toub的异步协调基元系列,但我不确定这是否是正确的方法。

2 个答案:

答案 0 :(得分:3)

您需要的是TaskCompletionSource<byte[]>用作同步构造,ConcurrentDictionary用于在ID和TCS以及侦听器之间进行映射:

ConcurrentDictionary<UniqueId, TaskCompletionSource<byte[]>> _dictionary;
async Task Listen(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        using (var ns = new NetworkStream(_socket))
        {
            byte[] responseBuffer = await ReceiveMessageAsync(ns);
            var id = ExtractId(responseBuffer);
            TaskCompletionSource<byte[]> tcs;
            if (dictionary.TryRemove(id, out tcs))
            {
                tcs.SetResult(responseBuffer);
            }
            else
            {
                // error
            }
        }
    }
}

Task RegisterTask(UniqueId id)
{
    var tcs = new TaskCompletionSource<byte[]>();
    if (!_dictionary.TryAdd(id, tcs))
    {
        // error
    }
    return tcs.Task;
}

然而,正如Stephen Cleary建议的那样,您可能希望使用现有的解决方案。

答案 1 :(得分:3)

因此,您需要将所有这些内容包装到一个新类中,因为您需要在您阅读的地点和您所写的地点之间共享状态。

每次去写入流时,您都需要接受唯一ID,并在查找中添加一个条目,将id映射到TaskCompletionSource。然后,write方法可以从该TCS返回Task

然后,您可以拥有一个单独的阅读器,它只是从您的流中读取,找到与该响应的ID关联的词典条目,并设置其结果。

public class MyNetworkStream : IDisposable
{
    private NetworkStream stream;
    private ConcurrentDictionary<int, TaskCompletionSource<byte[]>> lookup =
        new ConcurrentDictionary<int, TaskCompletionSource<byte[]>>();
    private CancellationTokenSource disposalCTS = new CancellationTokenSource();
    public MyNetworkStream(Socket socket)
    {
        stream = new NetworkStream(socket);
        KeepReading();
    }
    public void Dispose()
    {
        disposalCTS.Cancel();
        stream.Dispose();
    }

    public Task<byte[]> WriteAndWaitAsync(byte[] buffer, int offset, 
        int count, int uniqueID)
    {
        var tcs = lookup.GetOrAdd(uniqueID, new TaskCompletionSource<byte[]>());
        stream.WriteAsync(buffer, offset, count);
        return tcs.Task;
    }

    private async void KeepReading()
    {
        try
        {
            //TODO figure out what you want for a buffer size so that you can 
            //read a block of the appropriate size.
            byte[] buffer = null;
            while (!disposalCTS.IsCancellationRequested)
            {
                //TODO edit offset and count appropriately 
                await stream.ReadAsync(buffer, 0, 0, disposalCTS.Token);
                int id = GetUniqueIdFromBlock(buffer);
                TaskCompletionSource<byte[]> tcs;
                if (lookup.TryRemove(id, out tcs))
                    tcs.TrySetResult(buffer);
                else
                {
                    //TODO figure out what to do here
                }
            }
        }
        catch (Exception e)
        {
            foreach (var tcs in lookup.Values)
                tcs.TrySetException(e);
            Dispose();
            //TODO consider any other necessary cleanup
        }
    }

    private int GetUniqueIdFromBlock(byte[] buffer)
    {
        throw new NotImplementedException();
    }
}
相关问题