TPL数据流使用异步操作

时间:2012-06-12 16:30:24

标签: c# .net task-parallel-library dataflow tpl-dataflow

我正在尝试使用TPL数据流,通过移植一些旧的套接字代码来使用TPL数据流和新的异步功能。虽然API感觉非常坚固,但我的代码仍然感觉很乱。我想知道我在这里是否遗漏了什么。

我的要求如下:套接字类公开:打开,关闭,发送和接收方法。全部返回一个任务,因此是异步的。打开和关闭是原子的。发送和接收可以彼此相邻,尽管两者一次只能处理1个命令。

从逻辑上讲,这将带我进入内部控制的下一段代码:

// exposing an exclusive scheduler for connectivity related tasks and a parallel scheduler where send and receive can work with
private readonly ConcurrentExclusiveSchedulerPair exclusiveConnectionSchedulerPair;
private readonly ActionBlock<Action> connectionBlock;
private readonly ActionBlock<Action> sendBlock;
private readonly ActionBlock<Action> receiveBlock;

// within the constructor:
this.exclusiveConnectionSchedulerPair = new ConcurrentExclusiveSchedulerPair();
this.connectionBlock = new ActionBlock<Action>(action => action(), new ExecutionDataflowBlockOptions()  { TaskScheduler = exclusiveConnectionSchedulerPair.ExclusiveScheduler });
this.sendBlock = new ActionBlock<Action>(action => action(), new ExecutionDataflowBlockOptions()    { TaskScheduler = exclusiveConnectionSchedulerPair.ConcurrentScheduler });
this.receiveBlock = new ActionBlock<Action>(action => action(), new ExecutionDataflowBlockOptions() { TaskScheduler = exclusiveConnectionSchedulerPair.ConcurrentScheduler });

到目前为止一切都很好。我可以安全地将动作发送到发送和接收块,而不必担心同时运行连接相关的操作。此外,ActionBlock确保同步多个send调用(idem用于接收,关闭和打开)。

问题在于,没有简单的方法可以将任务传达回海报。现在我正在使用TaskCompletionSource来传回结果。像:

public Task Send(ArraySegment<byte> buffer, CancellationToken cancellationToken)
{
    TaskCompletionSource<object> resultCompletionSource = new TaskCompletionSource<object>();

    sendBlock.Post(async () =>
    {
        if (!tcpClient.Connected)
            throw new InvalidOperationException("Cant send when not open");
        else
        {
            await sendStream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken);
            resultCompletionSource.SetResult(null);
        }
    });

    return resultCompletionSource.Task;
}
只是觉得丑陋和笨拙。我的问题是:有没有办法使用TPL同步工作流而不必使用TaskCompletionSource进行中间的通信?

谢谢!

1 个答案:

答案 0 :(得分:4)

首先,您根本不需要TPL Dataflow,因为您实际上没有任何数据流。

其次,使用这样的TaskScheduler也不是正确的解决方案。 TaskScheduler的日程代码,但是当您await某事时,没有代码在运行。因此,在WriteAsync()执行异步工作时,Open()的代码可以运行。

您实际需要的是类似ReaderWriterLock的内容,但适用于async。框架中没有类似的东西,但你可以使用来自Stephen Toub's article Building Async Coordination Primitives, Part 7: AsyncReaderWriterLock的代码,它完全符合你的需要。本文还更详细地解释了为什么使用TaskScheduler是错误的。

使用AsyncReaderWriterLock,您的代码可能如下所示:

public async Task Send(ArraySegment<byte> buffer, CancellationToken cancellationToken)
{
    using (await readerWriterLock.ReaderLockAsync())
    {
        if (!tcpClient.Connected)
            throw new InvalidOperationException("Can't send when not open");

        await sendStream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken);
    }
}