2016-09-26 120 views
8

我有一個在ASP.NET MVC應用程序中使用的以下代碼示例。 這段代碼的目的是爲排隊一些長時間運行的操作創建「發送和忘記」請求。ASP.NET HttpContext.Current在Task.Run中

public JsonResult SomeAction() { 
    HttpContext ctx = HttpContext.Current;    

    Task.Run(() => { 
     HttpContext.Current = ctx; 
     //Other long running code here. 
    }); 

    return Json("{ 'status': 'Work Queued' }"); 
} 

我知道這是不是在異步代碼處理HttpContext.Current一個很好的方式,但目前我們的實現不會讓我們到別的做一些事情。 我想明白這個代碼是多少危險......

問題:是理論上可能設置的HttpContext內Task.Run,​​將上下文設置爲完全另一個請求?

我想是的,但我不確定。我的理解如下: Request1由線程池中的Thread1處理,然後當Thread1處理絕對另一個請求(Request2)時,Task.Run中的代碼將設置上下文從Request1到Request2。

也許我錯了,但是我對ASP.NET內部知識的瞭解並不能讓我正確理解它。

謝謝!

+3

從HttpContext獲取所需的信息,而不是傳入整個HttpContext會不會更簡單? (我意識到這並不能回答你的問題,但我對整個上下文的需求感到好奇)。 – vcsjones

+2

對,這是非常正確的方式,但不幸的是,目前我無法改變它。我們的代碼可以訪問HttpContext.Current內部的業務邏輯,並且改變它是目前我們沒有的巨大努力。 –

+1

與您的問題無關 - 在web上下文中執行長時間運行的任務並不是一個好主意 - 服務器可以重新啓動並且池中只有很多線程 - 一旦線程用完,您將停止提供請求。你有沒有考慮過像HangFire或Quartz? –

回答

5

讓我碰你一點內部:

public static HttpContext Current 
{ 
    get { return ContextBase.Current as HttpContext; } 
    set { ContextBase.Current = value; } 
} 

internal class ContextBase 
{ 
    internal static object Current 
    { 
     get { return CallContext.HostContext; } 
     set { CallContext.HostContext = value; } 
    } 
} 

public static object HostContext 
{ 
    get 
    { 
     var executionContextReader = Thread.CurrentThread.GetExecutionContextReader(); 
     object hostContext = executionContextReader.IllogicalCallContext.HostContext; 
     if (hostContext == null) 
     { 
      hostContext = executionContextReader.LogicalCallContext.HostContext; 
     } 
     return hostContext; 
    } 
    set 
    { 
     var mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext(); 
     if (value is ILogicalThreadAffinative) 
     { 
      mutableExecutionContext.IllogicalCallContext.HostContext = null; 
      mutableExecutionContext.LogicalCallContext.HostContext = value; 
      return; 
     } 
     mutableExecutionContext.IllogicalCallContext.HostContext = value; 
     mutableExecutionContext.LogicalCallContext.HostContext = null; 
    } 
} 

所以

var context = HttpContext.Current; 

等於(僞)

var context = CurrentThread.HttpContext; 

和裏面的Task.Run這樣的事情發生

CurrentThread.HttpContext= context; 

Task.Run將使用線程池中的線程啓動新任務。所以你告訴你的新線程「HttpContext屬性」是對啓動線程「HttpContext屬性」的引用 - 到目前爲止這麼好(所有的Nu​​llReference/Dispose異常你在初學線程完成後都會遇到)。問題是,如果你的

//Other long running code here. 

裏面你有一個像

var foo = await Bar(); 

一旦你打等待說法,當前的線程返回到線程池,和IO完成後,您從線程池中獲取新的話題 - 奇蹟它的「HttpContext屬性」是什麼,對吧?我不知道:)很可能你會以NullReferenceException結束。

+0

謝謝你的詳細答案!從你寫的內容來看,我明白技術上它可能發生(Request2將獲得Request1的上下文),但是最有可能的是我會得到空引用並處理異常,因爲實際上Request1已經完成並且ASP.NET「清除」了上下文。 ..我明白你的答案了嗎?謝謝! –

2

您將遇到的問題是,HttpContext將在請求完成時處置。由於您不是在等待Task.Run的結果,因此您本質上是在處理HttpContext和它在任務內的使用之間創建競爭條件。

我敢肯定,您的任務將遇到的唯一問題是NullReferenceException或ObjectDisposedException。我沒有看到任何方式可能會意外地竊取另一個請求的上下文。另外,除非你在你的任務中處理&日誌異常,否則你的火和遺忘將會拋出,你永遠不會知道它。

檢出HangFire或考慮使用消息隊列處理來自單獨進程的後端作業。

+0

感謝您的回答。我知道競爭條件和可能的NullReference/Disposed異常。但我想從技術上理解爲什麼你認爲這種方式不能竊取另一個請求上下文......就我所知,技術上HttpContext.Current通過線程Id來管理上下文,所以如果兩個請求將接收具有相同Id的線程,則HttpContext.Current可以從兩個請求中返回對同一對象的引用。 –