【发布时间】:2016-10-30 17:35:45
【问题描述】:
基本问题
在 ASP.NET MVC 5 下使用任务/线程时,HttpContext.Current 和/或其包含的实例变为空。这留下了例如Parallel Tasks 下的会话管理无用。我们将 User 实例存储在 Session 上。
经过大量阅读,我找到了一个适用于在普通循环中创建的任务和 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.For...:
Parallel.For(0, 1000, (idx) =>
{
/// Call ServiceLayer/DAL which needs Session["MyUser"]...
});
在调试器中,所有任务/线程都卡在 .Send 方法中。
问题
上述Tasks解决方案和Parallel.For有什么区别? Parallel.For 会阻塞 Request 线程吗?
欢迎帮助!
谢谢
编辑 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"];
在 Load with multiple Browsers 下使用插入 1000 个客户的 MVC 控制器请求对其进行了测试。插入一切正常。
谁能告诉我这是否是首选方式? 我知道 ASP.NET (MVC) 下的(长时间运行的)任务并不是一件好事,但我想知道它是否可行,也许可以用它来加快一些操作。
感谢您的反馈!
编辑 2
最少、完整和可验证的示例:
Example 数据访问层示例:
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 测试有效,但似乎很慢(呃):
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 下的并行任务比顺序任务快 3 倍。插入 1000 个客户。
在互联网上阅读表明 CallContext.Logical... 仅在 .NET 4.5+ 中是安全的,并且没有很好的文档记录(至少在 MSDN 中没有)
新问题: 是否专门将用户实例添加到逻辑 CallContext 流线程安全中?所以每个请求线程都必须使用 LogicalSetData 并且它的子任务/线程使用 LogicalGetData。
再次感谢您的反馈!
干杯
【问题讨论】:
-
如果 User 是您需要的上下文中唯一的数据,那么有更简单的方法可以传递它...
标签: c# asp.net-mvc multithreading parallel-processing