C# 使用异步操作的TPL数据流

C# 使用异步操作的TPL数据流,c#,.net,task-parallel-library,dataflow,tpl-dataflow,C#,.net,Task Parallel Library,Dataflow,Tpl Dataflow,我正在试验TPL数据流,通过移植一些旧的套接字代码来使用TPL数据流和新的异步特性。虽然API感觉坚如磐石,但我的代码最终还是感觉凌乱不堪。我想知道我是否遗漏了什么 我的要求如下:socket类公开:Open、Close、Send和Receive方法。它们都返回一个任务,因此是异步的。打开和关闭是原子的。虽然发送和接收一次只能处理一条命令,但它们可以相邻工作 从逻辑上讲,这就引出了下一段内部控制代码: // exposing an exclusive scheduler for connecti

我正在试验TPL数据流,通过移植一些旧的套接字代码来使用TPL数据流和新的异步特性。虽然API感觉坚如磐石,但我的代码最终还是感觉凌乱不堪。我想知道我是否遗漏了什么

我的要求如下:socket类公开:Open、Close、Send和Receive方法。它们都返回一个任务,因此是异步的。打开和关闭是原子的。虽然发送和接收一次只能处理一条命令,但它们可以相邻工作

从逻辑上讲,这就引出了下一段内部控制代码:

// 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 });
//为与连接相关的任务公开一个专用计划程序和一个并行计划程序,其中发送和接收可以使用
专用只读ConcurrentExclusiveSchedulerPair ExclusiveConnectionsSchedulerPair;
私有只读操作块连接块;
私有只读操作块sendBlock;
私有只读操作块receiveBlock;
//在构造函数内:
this.ExclusiveConnectionsSchedulerPair=新的ConcurrentExclusiveSchedulerPair();
this.connectionBlock=new ActionBlock(action=>action(),new ExecutionDataflowBlockOptions(){TaskScheduler=exclusiveConnectionSchedulerPair.ExclusiveScheduler});
this.sendBlock=new ActionBlock(action=>action(),new ExecutionDataflowBlockOptions(){TaskScheduler=exclusiveConnectionSchedulerPair.ConcurrentScheduler});
this.receiveBlock=新的ActionBlock(action=>action(),new ExecutionDataflowBlockOptions(){TaskScheduler=exclusiveConnectionSchedulerPair.ConcurrentScheduler});
到目前为止一切都很好。我可以安全地将操作发送到发送和接收块,而不必同时担心正在运行的连接相关操作。此外,ActionBlock确保多个要发送的调用是同步的(idem用于receive、close和open)

问题在于,对于一个动作来说,没有简单的方法将任务传达给海报。现在我正在使用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;
}
公共任务发送(ArraySegment缓冲区、CancellationToken CancellationToken)
{
TaskCompletionSource resultCompletionSource=新TaskCompletionSource();
sendBlock.Post(异步()=>
{
如果(!tcpClient.Connected)
抛出新的InvalidOperationException(“未打开时无法发送”);
其他的
{
wait sendStream.WriteAsync(buffer.Array、buffer.Offset、buffer.Count、cancellationToken);
resultCompletionSource.SetResult(null);
}
});
返回resultCompletionSource.Task;
}
感觉很难看很笨拙。我的问题是:有没有一种方法可以使用TPL来同步工作流,而不必使用TaskCompletionSource进行通信?


谢谢

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

其次,像这样使用
TaskScheduler
s也不是正确的解决方案
TaskScheduler
s调度代码,但是当您等待某个东西时,没有代码在运行。因此,当
WriteAsync()
执行异步工作时,
Open()
的代码可以运行

您实际需要的是类似的东西,但这在
async
中运行良好。框架中没有类似的东西,但是您可以使用中的代码,这正是您所需要的。本文还更详细地解释了为什么使用
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);
    }
}
公共异步任务发送(ArraySegment缓冲区、CancellationToken CancellationToken)
{
使用(等待readerWriterLock.ReaderLockAsync())
{
如果(!tcpClient.Connected)
抛出新的InvalidOperationException(“未打开时无法发送”);
wait sendStream.WriteAsync(buffer.Array、buffer.Offset、buffer.Count、cancellationToken);
}
}

我没有使用TPL数据流,所以这可能有点离题,但如果您需要知道操作何时完成,似乎您不想使用Post的“触发并忘记”,而是应该使用SendAsync,这样您就可以取回任务了?据我所知,当post被接受或明确拒绝时,SendAsync将返回一个任务。因为我使用的是无界动作块。职位将始终直接接受。SendAsync帮不上忙在阅读OP的问题时,我的第一个想法是链接到Toub的文章并给出一些背景。我犹豫不决,因为我记不起Toub在哪篇博客文章、白皮书等中写过这个问题。然后我看到这个答案已经精辟地解释了它,并链接到了确切的文章!你时不时地读到一个答案,你希望自己能多次投票!谢谢你的回答,虽然我现在还不满意。“打开”和“关闭”应仅从“发送”和“接收”以及“其他”运行。虽然异步rwlock在使用其中两个时可以工作,但它将是一个丑陋的解决方案。基于ConcurrentExclusiveSchedulerPair的调度任务似乎是为了实现整洁、轻松的同步。我认为您错误的假设是,Open应该能够在Send运行时运行,但事实并非如此。调用open时,任何正在运行的发送/接收都应该在实际打开之前完成。@我的观点是,如果您有
async
方法并使用
ConcurrentExclusiveSchedulerPair
,那么
open()
将能够在
Send()
等待时运行。但是,如果使用
AsyncReaderWriterLock
,则不会。我想那正是你想要的。@svick实际上不是。对不起,这不清楚。打开客户端时,不允许运行发送和接收操作。(因此,打开(连接)时发送不起作用)。另一方面,客户机能够在接收其他数据的同时发送数据。@Polity我知道这是您想要的,这就是我所说的