如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式?
此问题已由EF数据上下文-
异步/等待和多线程触发。我已经回答了一个,但是还没有提供任何最终解决方案。
最初的问题是,那里有很多有用的.NET API(例如Microsoft Entity Framework的API
DbContext
),它们提供了旨在与之一起使用的异步方法await
,但据记录它们
。这使其非常适合在桌面UI应用程序中使用,但不适用于服务器端应用程序。
这可能实际上并不适用DbContext
,这是Microsoft
关于EF6线程安全性的声明,请自行判断。
也有一些已建立的代码模式属于同一类别,例如使用OperationContextScope
(在此处和此处询问)调用WCF服务代理,例如:
using (var docClient = CreateDocumentServiceClient())using (new OperationContextScope(docClient.InnerChannel))
{
return await docClient.GetDocumentAsync(docId);
}
这可能会失败,因为OperationContextScope
在其实现中使用线程本地存储。
问题的根源AspNetSynchronizationContext
是在异步ASP.NET页中使用它来以更少的线程ASP.NET
池线程来满足更多的HTTP请求。使用AspNetSynchronizationContext
,await
可以在与发起异步操作的线程不同的线程上排队一个延续,同时将原始线程释放到池中,并可以用于服务另一个HTTP请求。
该机制在必须阅读的“关于同步上下文的全部内容”中进行了详细描述。因此,尽管 涉及
,但是潜在的线程切换仍然阻止我们使用上述API。
显然,收回这些API的唯一方法是针对可能受到线程切换影响的异步调用范围
。
假设我们具有这种线程相似性。无论如何,这些调用中的大多数都是受IO约束的(没有线程)。当异步任务挂起时,它所源自的线程可用于服务另一个类似任务的延续,该结果已经可用。因此,它不应过多地损害可伸缩性。这种方法并不是什么新鲜事物,实际上,
。IMO,这是使Node.js如此受欢迎的原因之一。
我不明白为什么这种方法不能在ASP.NET上下文中使用。定制任务调度程序(我们称之为ThreadAffinityTaskScheduler
)可以维护一个单独的“亲和公寓”线程池,以进一步提高可伸缩性。一旦任务已排队到那些“公寓”线程之一中,await
任务内部的所有继续将在同一线程上进行。
这是链接问题中的非线程安全API
可以与此类方法一起使用的方式ThreadAffinityTaskScheduler
:
// create a global instance of ThreadAffinityTaskScheduler - per web apppublic 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 Toub的出色表现,我继续进行并
StaTaskScheduler
。ThreadAffinityTaskScheduler
在经典的COM意义上,由其维护的池线程不是STA线程,但是它们确实实现了线程的await
连续性(SingleThreadSynchronizationContext
对此负责)。
到目前为止,我已经将该代码作为控制台应用程序进行了测试,并且看起来可以按设计工作。我尚未在ASP.NET页面中对其进行测试。我没有大量的ASP.NET生产开发经验,所以我的问题是:
在ASP.NET中非线程安全的API的简单 调用上使用这种方法是否有意义(主要目标是避免牺牲可伸缩性)?
除了使用同步API调用或完全避免这些APis之外,还有其他方法吗?
有没有人在ASP.NET MVC或Web API项目中使用过类似的东西,并准备分享他/她的经验?
任何有关如何使用ASP.NET进行压力测试和概要分析的建议都将受到赞赏。
回答:
实体框架将(应该)处理跨await
点的线程跳转。如果没有,那是EF中的错误。OTOH
OperationContextScope
基于TLS,并且await
不安全。
1.同步API维护您的ASP.NET上下文;这包括诸如用户身份和文化等在处理过程中通常很重要的东西。另外,许多ASP.NET
API都假定它们在实际的ASP.NET上下文中运行(我并不是说仅使用HttpContext.Current
;我是说实际上是假定它SynchronizationContext.Current
是的一个实例AspNetSynchronizationContext
)。
2-3。我使用了直接嵌套在ASP.NET上下文中的我自己的单线程上下文,以尝试使async
MVC子操作起作用而不必重复代码。但是,不仅会失去可伸缩性的好处(至少对于请求的那部分而言),而且假设ASP.NET
API在ASP.NET上下文中运行,您还会遇到它们。
因此,我从未在生产中使用这种方法。我只是在必要时使用了同步API。
以上是 如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式? 的全部内容, 来源链接: utcz.com/qa/398597.html