基本問題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中了。
我們的IoC容器統一的註冊一起:
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。
再次感謝您的反饋!
乾杯
如果用戶是上下文中唯一需要的數據,那麼有更簡單的方法將其傳遞... –