这个问题是由EF数据上下文-异步/等待和多线程触发的。我已经回答了这个问题,但没有提供任何最终的解决方案。
最初的问题是,有很多有用的.NET API(比如Microsoft的DbContext),它们提供了设计用于await的异步方法,但是它们被记录为而不是线程安全的。这使得它们非常适合桌面用户界面应用程序,但不适合服务器端应用程序。编辑的--这可能并不适用于DbContext__,这是微软的https://entityframework.codeplex.com/wikipage?title=Task-based%20Asynchronous%20Pattern%20support%20in%20EF.#ThreadSafety,你自己来判断吧。/EDITED
还有一些已建立的代码模式属于同一类别,例如使用OperationContextScope调用WCF服务代理(询问这里和这里),例如:
using (var docClient = CreateDocumentServiceClient())
using (new OperationContextScope(docClient.InnerChannel))
{
return await docClient.GetDocumentAsync(docId);
}这可能会失败,因为OperationContextScope在其实现中使用线程本地存储。
问题的根源是AspNetSynchronizationContext,它用于异步ASP.NET页面,以满足更多的ASP.NET请求,同时减少来自ASP.NET线程池的线程。使用AspNetSynchronizationContext,可以在与启动异步操作的线程不同的线程上排队,而将原始线程释放到池中并用于服务另一个HTTP请求。这大大提高了服务器端代码的可伸缩性。在都是关于SynchronizationContext的 (必读)中详细描述了这种机制。因此,虽然不涉及并发API访问,但潜在的线程切换仍然阻止我们使用上述API。
--我一直在考虑如何在不牺牲可伸缩性的情况下解决这个问题。显然,获得这些API的唯一方法是维护线程关联,以适应可能受到线程开关影响的异步调用的范围。
假设我们有这样的线程亲和力。这些调用大多数都是IO绑定的(没有线程)。当异步任务挂起时,它所处的线程可以用来继续执行另一个类似的任务,该任务的结果已经可用。因此,它不应该对可伸缩性造成太大的损害。这种方法并不新鲜,事实上,类似的单线程模型是 Node.js成功使用.海事组织,这是使Node.js如此受欢迎的原因之一。
我不明白为什么不能在ASP.NET上下文中使用这种方法。定制的任务调度程序(我们称之为ThreadAffinityTaskScheduler)可能会维护一个单独的“关联单元”线程池,以进一步提高可伸缩性。一旦任务排队到其中一个“公寓”线程,任务中的所有await延续都将发生在同一个线程上。
下面是来自关联问题的非线程安全API如何与这样的ThreadAffinityTaskScheduler一起使用:
// create a global instance of ThreadAffinityTaskScheduler - per web app
public static class GlobalState
{
public static ThreadAffinityTaskScheduler TaScheduler { get; private set; }
public static GlobalState
{
GlobalState.TaScheduler = new ThreadAffinityTaskScheduler(
numberOfThreads: 10);
}
}
// ...
// run a task which uses non-thread-safe APIs
var result = await GlobalState.TaScheduler.Run(() =>
{
using (var dataContext = new DataContext())
{
var something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);
// ...
// transform "something" and "morething" into thread-safe objects and return the result
return data;
}
}, CancellationToken.None);我在Stephen的优秀ThreadAffinityTaskScheduler 的基础上,将其作为概念的证明。ThreadAffinityTaskScheduler维护的池线程不是传统COM意义上的STA线程,但它们实现了await延续的线程关联(SingleThreadSynchronizationContext负责)。
到目前为止,我已经将这段代码作为控制台应用程序进行了测试,它似乎按照设计的方式工作。我还没有在ASP.NET页面中测试它。我没有太多的生产ASP.NET开发经验,所以我的问题是:
发布于 2014-01-08 12:48:41
实体框架将(应该)处理跨越await点的线程;如果没有,那就是EF中的一个bug。OTOH,OperationContextScope是基于TLS而不是await-safe的。
HttpContext.Current;我的意思是实际上假设SynchronizationContext.Current是AspNetSynchronizationContext的一个实例)。2-3。我使用了直接嵌套在我自己的单线程上下文上下文中的ASP.NET,以获得MVC子动作工作而不必重复代码。但是,您不仅失去了可伸缩性的好处(至少对于请求的这一部分而言),还会在假设ASP.NET API在ASP.NET上下文上运行的情况下运行它们。
所以,我从来没有在生产中使用过这种方法。我只是在必要时使用了同步API。
https://stackoverflow.com/questions/20993007
复制相似问题