4

TPLデータフローと新しい非同期機能を使用するために、古いソケットコードを移植して、TPLデータフローを試しています。APIは堅実に感じられますが、私のコードはそれでも厄介な感じになります。ここで何かが足りないのではないかと思います。

私の要件は次のとおりです。ソケットクラスは、Open、Close、Send、Receiveメソッドを公開します。すべてがタスクを返すため、非同期です。開くと閉じるはアトミックです。送信と受信は互いに隣接して機能しますが、どちらも一度に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 });

これまでのところすべて良い。その間に接続関連のアクションが実行されていることを心配することなく、SendandReceiveブロックにアクションを安全に送信できます。また、ActionBlockは、送信する複数の呼び出しが同期されることを保証します(受信、クローズ、およびオープンの理想)。

問題は、アクションがタスクを投稿者に伝える簡単な方法がないことです。現在、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を使用する必要はありませんか?

ありがとう!

4

1 に答える 1

5

まず、実際にはデータフローがないため、これにはTPLデータフローはまったく必要ありません。

第二に、TaskSchedulerこのようなsを使用することも正しい解決策ではありません。TaskScheduler■スケジュールコードですが、await何かをしている間は、実行中のコードはありません。したがって、WriteAsync()非同期作業を行っている間、forのコードOpen()が実行される可能性があります。

実際に必要なのはのようなものですがReaderWriterLock、それは。でうまく機能しasyncます。フレームワークにはそのようなものはありませんが、StephenToubの記事BuildingAsync Coordination Primitives、Part 7:AsyncReaderWriterLockのコードを使用できます。これは、まさに必要なことを実行します。TaskSchedulerこの記事では、aの使用が間違っている理由についても詳しく説明しています。

を使用する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);
    }
}
于 2012-06-12T16:58:38.003 に答える