ASP.NET MVC 5同步HttpContext和Parallel.对于(每个)都可以工作
本文关键字:对于 每个 工作 都可以 Parallel MVC NET 同步 HttpContext ASP | 更新日期: 2023-09-27 18:15:31
基本问题
在ASP.NET MVC 5下使用Tasks/Threads时,HttpContext.Current和/或其包含的实例将变为null。这使得会话管理在并行任务下变得毫无用处。我们将用户实例存储在会话中。
经过大量阅读,我找到了一个解决方案,它可以与在纯循环和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());
神奇之处在于在存储的SynchronizationContext上使用Send方法:它在原始请求线程中运行代码/操作。就像UI线程在WinForms:中一样
User myUser = null;
SynchronizationContext requestSyncContext = (SynchronizationContext)CallContext.LogicalGetData("requestSyncContext");
if (requestSyncContext != null)
{
requestSyncContext.Send( (state) =>
{
myUser = (User)HttpContext.Current.Session["MyUser"];
}, null);
}
最终问题
我已经测试了上面的解决方案,它适用于同步和异步(等待(任务。但不适用于Parallel。适用于…:
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 Unity一起注册:
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控制器请求,在多个浏览器的负载下对其进行了测试。Al插件正常。
有人能告诉我这是不是最好的方式吗?我知道在ASP.NET(MVC(下(长时间运行(任务并不是一件好事,但我想知道这是否可能,也许可以用来加快一些操作。
感谢您的反馈!
编辑2
最小、完整且可验证的示例:
示例数据访问层示例:
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!");
}
}
并行示例。用于失败的测试。
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");
}
示例并行。对于有效但似乎很慢的测试(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");
}
启动任务的循环:失败
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.LLogicalGetData来研究这个问题。第一次测试表明,ASP.NET MVC 5下的并行任务比Sequential快3倍。插入1000 x 1个客户。
互联网上的阅读表明,CallContext.Logical…仅在.NET 4.5+中是安全的,并且没有很好的文档记录(至少在MSDN中没有(
新问题:专门将用户实例添加到逻辑调用上下文流中是线程安全的吗?因此,每个请求线程都必须使用LogicalSetData,其子任务/线程使用LogicalGetData。
再次感谢您的反馈!
干杯
Parallel.For会阻塞请求线程吗?
当你从那个线程调用它时,那么:是的。
我认为AsyncLocal将是一个更好的选择,它将把值复制到所有子线程/任务中,但当你在任何子线程中更改它时,其他线程都看不到该值,除非你保存的值是一个对象,并且你只更改了该对象的值,否则它对其他线程都是可见的。