2016-10-30 69 views
1

基本問題ASP.NET MVC 5同步HttpContext和Parallel.For(每個)它可以工作嗎?

當使用任務/下ASP.NET MVC 5線程的HttpContext.Current和/或其包含的情況下,成爲空。這例如離開會話管理在「並行任務」下無用。我們將我們的用戶實例存儲在會話中。

經過大量閱讀後,我找到了一個解決方案,該解決方案適用於在純循環和RunSynchronously中創建的任務。但由於不明原因,Parallel.For陷入了僵局。

目前的解決方案

我目前的解決方案是基於SynchronizationContext.Current被設置爲請求線程,併爲它的「孩子」任務/線程未設置。在請求線程我把當前的SynchronizationContext到CallContext.LogicalSetData,啓動所有任務:

 CallContext.LogicalSetData("HttpRequestSyncContext", SynchronizationContext.Current); 

...

 List<Task> tasks = new List<Task>(); 
     for (int lc = 0; lc < 1000; lc++) 
     { 
      tasks.Add(new Task(() => 
      { 
       /// Call ServiceLayer/DAL which needs Session["MyUser"]... 
      }, CancellationToken.None, TaskCreationOptions.LongRunning)); 
     } 

     tasks.ForEach(t => t.RunSynchronously()); 

     Task.WaitAll(tasks.ToArray()); 

神奇的附帶使用Send方法對存儲的SynchronizationContext:它運行原始請求線程中的代碼/操作。就像UI線程做它的WinForms:

 User myUser = null; 
     SynchronizationContext requestSyncContext = (SynchronizationContext)CallContext.LogicalGetData("requestSyncContext"); 
     if (requestSyncContext != null) 
     { 
      requestSyncContext.Send((state) => 
       { 
        myUser = (User)HttpContext.Current.Session["MyUser"]; 
       }, null); 
     } 

最後一個問題

我上面的解決方案測試,它同時適用於synchroon和異步(的await)任務。但不適用於Parallel.For ...:

 Parallel.For(0, 1000, (idx) => 
     { 
      /// Call ServiceLayer/DAL which needs Session["MyUser"]... 
     }); 

在調試器中,所有任務/線程都卡在.Send方法中。

問題

是什麼上述任務的解決方案和的Parallel.For之間的區別? Parallel.For是否阻止請求線程?

歡迎光臨!

感謝

編輯1

偶然發現了什麼看起來像一個解決方案:

 ParallelOptions pOptions = new ParallelOptions 
     { 
      TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() 
     }; 

     Parallel.For(0, 1000, pOptions, (idx) => 
     { 
     ... 

是不需要的SynchronizationContext和CallContext中了。

我們的Io​​C容器統一的註冊

一起:

 container.RegisterType<HttpContextBase>(
      new PerRequestLifetimeManager(), 
      new InjectionFactory(x => { return new HttpContextWrapper(HttpContext.Current); }) 
     ); 

和決心:

 HttpContextBase httpCtx = ServiceLayer.Container.Resolve<HttpContextBase>(); 
     return (User)httpCtx.Session["MyUser"]; 

與該插入1000級的客戶一個MVC控制器的請求多個瀏覽器測試過負荷狀態下。鋁插入確定。

有人可以告訴我,如果這是最好的方式去? 我知道ASP.NET(MVC)下的(長時間運行)任務並不是一件好事,但我想知道它是否可行,並可能用它來加速一些操作。

感謝您的反饋!

編輯2

最小,完全和實施例Verifyable(一個或多個):

實施例的數據訪問層例如:

public void NeedsPresentationLayerUser() 
{ 
    // Some work e.g. DB calls 

    // Need User from Presentation layer 
    HttpContextBase httpCtx = ServiceLayer.Container.Resolve<HttpContextBase>(); 
    string userName = (string)httpCtx.Session["MyUser"]; 

    if (!userName.Equals("Me")) 
    { 
     throw new ApplicationException("Assert: UserName test failed!"); 
    } 
} 

實施例的Parallel.For測試失敗。

public ActionResult MCVParallelTestFail() 
{ 
    Session["MyUser"] = "Me"; 

    Parallel.For(0, 1000, (idx) => 
    { 
     // Call down into Data Access layer... 
     ServiceLayer.Db.SystemFactory.NeedsPresentationLayerUser(); 
    }); 

    return RedirectToAction("Index", "Home"); 
} 

例的Parallel.For測試工作,但似乎慢(ER):

public ActionResult MCVParallelTestWorks() 
{ 
    Session["MyUser"] = "Me"; 

    ParallelOptions pOptions = new ParallelOptions 
    { 
     TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() 
    }; 

    Parallel.For(0, 1000, pOptions, (idx) => 
    { 
     // Call down into Data Access layer... 
     ServiceLayer.Db.SystemFactory.NeedsPresentationLayerUser(); 
    }); 

    return RedirectToAction("Index", "Home"); 
} 

普通的for循環啓動任務:失敗

public ActionResult MCVTasksTestFail() 
{ 
    Session["MyUser"] = "Me"; 

    for(int lc = 0; lc < 1000; lc++) 
    { 
     Task.Factory.StartNew(() => 
     { 
      // Call down into Data Access layer... 
      ServiceLayer.Db.SystemFactory.NeedsPresentationLayerUser(); 
     }); 
    } 

    return RedirectToAction("Index", "Home"); 
} 

同一平面循環,但現在用.RunSynchronously()啓動任務:

public ActionResult MCVTasksTestWorks() 
    { 
     Session["MyUser"] = "Me"; 

     List<Task> tasks = new List<Task>(); 
     for(int lc = 0; lc < 1000; lc++) 
     { 
      tasks.Add(new Task(() => 
      { 

       // Call down into Data Access layer... 
       ServiceLayer.Db.SystemFactory.NeedsPresentationLayerUser(); 

      })); 
     } 

     tasks.ForEach(t => t.RunSynchronously()); 
     Task.WaitAll(tasks.ToArray()); 

     return RedirectToAction("Index", "Home"); 
    } 

正如Henk Holterman指出的,還有其他方法可以獲得User實例,而無需在整個流程中提供上下文變量。我們現在使用CallContext.LogicalSetData/CallContext.LogicalGetData來查看它。第一個測試表明ASP.NET MVC 5下的並行任務比Sequential快3倍。插入1000 x一個客戶。

閱讀互聯網上表明CallContext.Logical ......僅在.NET安全4.5+,而不是非常有據可查(至少在MSDN)

新問題: 是專門添加用戶實例轉換爲邏輯CallContext流線程安全?所以每個請求線程必須使用LogicalSetData,並且其子任務/線程使用LogicalGetData。

再次感謝您的反饋!

乾杯

+1

如果用戶是上下文中唯一需要的數據,那麼有更簡單的方法將其傳遞... –

回答

1

是否的Parallel.For塊請求的線程?

當你從該線程調用它時,則:是。

+0

但是使用任務解決方案,它使用waitall,然後發送不會阻止!所以等待任務似乎表現得不同於...並行。 –

+0

更奇怪,因爲你用RunSynchronously執行它們。沒有周圍的代碼,我無法進一步瞭解這一點。考慮創建一個[MCVE] –

相關問題