2012-06-14 100 views
11

我很喜歡在調用內部處理程序的SendAsync()之前執行同步工作,並在內部處理程序通過完成完成後執行同步工作。例如:DelegatingHandler如何進行異步調用(ASP.NET MVC Web API)?

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約束任務異步然後繼續異步執行內部處理?

關鍵是我想確保請求線程在任何時候都不會被阻塞。

回答

15

好吧,我想我有這個破解。我用身份驗證方案說明了這一點:我想異步驗證用戶,並使用結果來決定是返回401還是繼續使用消息處理程序鏈。

的中心問題是,你不能調用內處理SendAsync(),直到你從異步驗證結果。

對我來說,關鍵的洞察是使用TaskCompletionSource(TCS)來控制執行流程。這使我能夠從TCS返回任務,並隨時在其上設置結果 - 並且最重要的是將呼叫延遲SendAsync(),直到我知道我需要它爲止。

因此,我設置了TCS,然後開始執行授權任務。在繼續這個,我看看結果。如果被授權,我調用內部處理程序鏈並附加一個延續到這個完成TCS的(避免任何線程阻塞)。如果驗證失敗,我只是完成了TCS那裏,然後用這個401

結果是異步任務又沒有任何線程阻塞執行。我加載測試這個,它似乎工作得很好。

雖然.NET 4.5中的異步/等待語法雖然...雖然TCS的方法仍然基本上在封面上發生,但代碼要簡單得多。

享受!

第一個片段是基於.NET 4.0和Web API Beta構建的 - 第二個是.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; 
} 

這是一個.NET 4.5 /網頁API發佈候選版本,這是性感了很多新的異步/ AWAIT語法:

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); 
} 
+0

你是怎麼實現的'Authorize'?你有沒有實現一個新的'HttpClient'? – JobaDiniz

+0

Authorize的實現在這裏與此無關 - 它只是一個異步函數,用於確定請求是否被允許 - 您如何實現它取決於您。例如,可能會進行數據庫檢查,以查看當前用戶是否有權根據某些定製的業務規則進行當前請求。 –

+0

我只是想知道你是否做了另一個http請求,這就是所有......在我的場景中,我需要在當前的一個http調用之前進行另一個http調用,並且實例化另一個'HttpClient'。我想知道是否可以在處理程序中重新使用當前的程序,但似乎我不能 – JobaDiniz