2017-04-24 80 views
7

我有一個針對.NET 4.6的ASP.NET應用程序,我瘋了試圖找出爲什麼HttpContext.Current在我的異步MVC控制器內第一次等待之後變爲空行動。ASP.NET 4.6異步控制器方法在等待後丟失HttpContext.Current

我檢查並三重檢查了我的項目是針對v4.6,並且web.config的targetFramework屬性也是4.6。

SynchronizationContext.Current在等待之前和之後都被分配,它是正確的,即AspNetSynchronizationContext,而不是傳統的。

FWIW,有問題的等待線程繼續切換線程,這可能是因爲它調用外部I/O綁定代碼(異步數據庫調用),但這不應該是一個問題,AFAIU。

然而,它是! HttpContext.Current變爲空的事實會導致我的代碼出現很多問題,並且對我沒有任何意義。

我檢查了the usual recommendations,我很積極我正在做我應該做的一切。我的代碼中絕對沒有ConfigureAwait

我做什麼都有,是一對夫婦的異步事件處理程序對我HttpApplication例如:

public MvcApplication() 
{ 
    var helper = new EventHandlerTaskAsyncHelper(Application_PreRequestHandlerExecuteAsync); 
    AddOnPreRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); 

    helper = new EventHandlerTaskAsyncHelper(Application_PostRequestHandlerExecuteAsync); 
    AddOnPostRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); 
} 

我需要因爲自定義授權&清理邏輯,這就需要異步兩種。 AFAIU,這是支持的,不應該是一個問題。

還有什麼可能是我看到這令人費解的行爲的原因?

更新:額外的觀察。

SynchronizationContext參考在等待和等待之前保持不變。但它的內部變化在下面的截圖中可以看出!

前AWAIT: Before entering await

後AWAIT: After continuing

我不知道如何(或者即使),這可能在這一點上是有關我的問題。希望別人能看到它!

+1

您是否在您的控制器操作上調用'ConfigureAwait(false)'? –

+0

@Paulo:不,我不是。 – aoven

+1

@Eldho:那會是什麼意思?我需要並希望控制器上的異步操作完全是因爲內部組件(即我提到的數據庫調用,例如)是異步的。刪除異步需要我禁用大量的代碼。 – aoven

回答

0

我決定在HttpContext.Current上定義一個手錶,並開始步入「等待」,看看它發生了什麼變化。毫不奇怪,當我繼續時,線程被多次切換,這對我來說很有意義,因爲在途中有多個真正的異步調用。他們都保留了他們應該的HttpContext.Current實例。

然後我打的那一行...

var observer = new EventObserver(); 
using (EventMonitor.Instance.Observe(observer, ...)) 
{ 
    await plan.ExecuteAsync(...); 
} 

var events = await observer.Task; // Doh! 

簡短的解釋是:plan.ExecuteAsync執行多種,它們通過一個專用的線程報告給專門的事件日誌中非阻塞的方式步驟。這是商業軟件,報告事件的模式在整個代碼中被廣泛使用。大多數情況下,這些事件並沒有直接關係到呼叫者。但是有一個或兩個地方是特殊的,因爲調用者想知道執行某個代碼後發生了哪些事件。正如上面所看到的那樣,當使用EventObserver實例時。

await observer.Task是必要的,爲了等待所有相關事件的處理和觀察。有問題的任務來自觀察員擁有的TaskCompletionSource實例。一旦所有事件都已經進入,源代碼SetResult將從處理事件的線程中調用。我原來執行這個細節是 - 很天真 - 如下:

public class EventObserver : IObserver<T> 
{ 
    private readonly ObservedEvents _events = new ObservedEvents(); 

    private readonly TaskCompletionSource<T> _source; 

    private readonly SynchronizationContext _capturedContext; 

    public EventObserver() 
    { 
     _source = new TaskCompletionSource<T>(); 

     // Capture the current synchronization context. 
     _capturedContext = SynchronizationContext.Current; 
    } 

    void OnCompleted() 
    { 
     // Apply the captured synchronization context. 
     SynchronizationContext.SetSynchronizationContext(_capturedContext); 
     _source.SetResult(...); 
    } 
} 

我現在可以看到調用SetSynchronizationContextSetResult之前沒有做什麼,我希望這將是。目標是將原始同步上下文應用於行await observer.Task的繼續。

現在的問題是:我該如何做到這一點?我猜這將需要一個明確的ContinueWith電話。

UPDATE

這裏就是我所做的。我通過了TaskCreationOptions.RunContinuationsAsynchronously選項TaskCompletionSource構造函數和修改我EventObserver類的任務屬性包括明確地同步續:

public Task<T> Task 
{ 
    get 
    { 
     return _source.Task.ContinueWith(t => 
     { 
      if (_capturedContext != null) 
      { 
       SynchronizationContext.SetSynchronizationContext(_capturedContext); 
      } 

      return t.Result; 
     }); 
    } 
} 

所以現在,當代碼調用await observer.Task,繼續將確保在第一次進入正確的上下文。到目前爲止,它似乎工作正常!

+2

另一種選擇是將調用發佈到SetResult到同步上下文。例如:'_capturedContext.Post(_ => _source.SetResult(...),null);' –