13

内部ハンドラーの SendAsync() を呼び出す前に同期作業を実行し、完了によって内部ハンドラーが完了した後に同期作業を実行することに慣れています。例えば:

protected override Task<HttpResponseMessage> SendAsync( 
    HttpRequestMessage request, CancellationToken cancellationToken) 
{ 
    // do some sync work before inner handler here 

    var sendTask = base.SendAsync(request, cancellationToken); 
    return sendTask.ContinueWith( 
        task => { // do some sync work afterwards here }); 
} 

ただし、委任ハンドラー内から IO バウンド操作を呼び出す必要があります。IO バウンド操作は、すでに としてラップされていTask<bool>ます。結果を使用して、内部ハンドラーに進むかどうかを判断する必要があります。

例としては、リクエストを承認するためにネットワーク呼び出しを行うことがあります。既存のシステムと統合するには、これを行う必要があります。一般に、この問題には有効なシナリオがあり、実行可能な解決策があるはずです。

この場合、SendAsync を実装する正しい方法は何ですか?IO バインドされたタスクを非同期的に実行し、内部ハンドラーを非同期的に実行し続けるにはどうすればよいでしょうか?

重要な点は、要求スレッドがどの時点でもブロックされたままにならないようにすることです。

4

1 に答える 1

17

OK、私はこれにひびが入ったと思います。認証シナリオでこれを説明しています。ユーザーを非同期的に認証し、その結果を使用して 401 を返すか、メッセージ ハンドラー チェーンを続行するかを決定します。

中心的な問題は、非同期認証の結果が得られるまで、内部ハンドラー SendAsync() を呼び出せないことです。

私にとって重要な洞察は、TaskCompletionSource (TCS) を使用して実行の流れを制御することでした。これにより、TCS から Task を返し、好きなときに結果を設定できるようになりました。最も重要なことは、必要になるまで SendAsync() の呼び出しを遅らせることでした。

そこで、TCS をセットアップしてから、承認を行うタスクを開始しました。この続きで、結果を見ていきます。許可されている場合は、内部のハンドラー チェーンを呼び出し、TCS を完了する(スレッドのブロックを回避する)継続をこれにアタッチします。認証が失敗した場合は、そこで TCS を完了してから 401 を送信します。

この結果、両方の非同期タスクがスレッドのブロックなしで順番に実行されます。これを負荷テストしたところ、問題なく動作するようです。

ただし、async/await 構文を使用する .NET 4.5 ではすべてがはるかに優れています... TCS を使用したアプローチは基本的にまだ内部で行われていますが、コードははるかに単純です。

楽しみ!

最初のスニペットは Web API Beta を使用して .NET 4.0 で構築され、2 番目のスニペットは .NET 4.5/Web API RC で構築されました。

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();

    // Authorize() returns a started
    // task that authenticates the user
    // if the result is false we should
    // return a 401 immediately
    // otherwise we can invoke the inner handler
    Task<bool> authenticationTask = Authorize(request);

    // attach a continuation...
    authenticationTask.ContinueWith(_ =>
    {
        if (authenticationTask.Result)
        {
            // authentication succeeded
            // so start the inner handler chain
            // and write the result to the
            // task completion source when done
            base.SendAsync(request, cancellationToken)
                .ContinueWith(t => taskCompletionSource.SetResult(t.Result));
        }
        else
        {
            // authentication failed
            // so complete the TCS immediately
            taskCompletionSource.SetResult(
                new HttpResponseMessage(HttpStatusCode.Unauthorized));
        }
    });

    return taskCompletionSource.Task;
}

これは、新しい async/await 構文を備えた、より魅力的な .NET 4.5 / Web API Release Candidate バージョンです。

protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    // Authorize still has a Task<bool> return type
    // but await allows this nicer inline syntax
    var authorized = await Authorize(request);

    if (!authorized)
    {
        return new HttpResponseMessage(HttpStatusCode.Unauthorized)
        {
            Content = new StringContent("Unauthorized.")
        };
    }

    return await base.SendAsync(request, cancellationToken);
}
于 2012-06-18T10:13:23.723 に答える