C# 如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式?
这个问题是由以下因素引发的。我已经回答了这个问题,但还没有提供任何最终的解决方案 最初的问题是有很多有用的.NET API(如Microsoft Entity Framework),它们提供了异步方法,设计用于C# 如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式?,c#,asp.net,.net,task-parallel-library,async-await,C#,Asp.net,.net,Task Parallel Library,Async Await,这个问题是由以下因素引发的。我已经回答了这个问题,但还没有提供任何最终的解决方案 最初的问题是有很多有用的.NET API(如Microsoft Entity Framework),它们提供了异步方法,设计用于等待,但它们被记录为非线程安全。这使得它们非常适合在桌面UI应用程序中使用,但不适用于服务器端应用程序[编辑]这可能实际上不适用于DbContext,这是微软的,请自己判断[/EDITED] 还有一些已建立的代码模式属于同一类别,例如使用(ask and)调用WCF服务代理,例如: 使用(
等待
,但它们被记录为非线程安全。这使得它们非常适合在桌面UI应用程序中使用,但不适用于服务器端应用程序[编辑]这可能实际上不适用于DbContext
,这是微软的,请自己判断[/EDITED]
还有一些已建立的代码模式属于同一类别,例如使用(ask and)调用WCF服务代理,例如:
使用(var docClient=CreateDocumentServiceClient())
使用(新OperationContextScope(docClient.InnerChannel))
{
返回wait-docClient.getdocumentsync(docId);
}
这可能会失败,因为OperationContextScope
在其实现中使用线程本地存储
问题的根源是AspNetSynchronizationContext
,它用于异步ASP.NET页面中,以ASP.NET
线程池中的较少线程来满足更多HTTP请求。使用AspNetSynchronizationContext
,wait
延续可以在启动异步操作的线程之外的另一个线程上排队,而原始线程被释放到池中,并可用于服务另一个HTTP请求这大大提高了服务器端代码的可伸缩性。必读的中详细介绍了该机制。因此,虽然不涉及并发API访问,但潜在的线程切换仍然阻止我们使用上述API
我一直在考虑如何在不牺牲可伸缩性的情况下解决这个问题。显然,让这些API返回的唯一方法是为可能受线程切换影响的异步调用范围维护线程关联性
假设我们有这样的线程亲和力。大多数调用都是IO绑定的()。当一个异步任务挂起时,它发起的线程可以用于服务另一个类似任务的继续,该结果已经可用。因此,它不应该对可伸缩性造成太大的伤害。这种方法并不新鲜,事实上,类似的单线程模型是。在我看来,这正是Node.js如此流行的原因之一
我不明白为什么这种方法不能在ASP.NET环境中使用。自定义任务调度程序(我们称之为ThreadAffinityTaskScheduler
)可能会维护一个单独的“affinityplant”线程池,以进一步提高可伸缩性。一旦任务已排队到其中一个“单元”线程,任务内的所有wait
continuation都将在同一个线程上进行
下面介绍如何将来自的非线程安全API与此类ThreadAffinityTaskScheduler
一起使用:
//为每个web应用创建ThreadAffinityTaskScheduler的全局实例
公共静态类GlobalState
{
公共静态ThreadAffinityTaskScheduler TaScheduler{get;private set;}
公共静态全局状态
{
GlobalState.TaScheduler=新线程关联taskscheduler(
线程数:10);
}
}
// ...
//运行使用非线程安全API的任务
var result=await GlobalState.TaScheduler.Run(()=>
{
使用(var dataContext=new dataContext())
{
var something=await dataContext.someEntities.FirstOrDefaultAsync(e=>e.Id==1);
var morething=await dataContext.someEntities.FirstOrDefaultAsync(e=>e.Id==2);
// ...
//将“something”和“morething”转换为线程安全对象并返回结果
返回数据;
}
},CancellationToken.None);
我根据Stephen Toub的优秀作品继续进行了作为概念证明。ThreadAffinityTaskScheduler
维护的池线程不是典型COM意义上的STA线程,但它们确实实现了wait
continuations的线程关联(SingleThreadSynchronizationContext
负责)
到目前为止,我已经将这段代码作为控制台应用程序进行了测试,它似乎按照设计工作。我还没有在ASP.NET页面中测试它。我没有很多生产ASP.NET开发经验,因此我的问题是:
等待
点的线程跳转;如果没有,那就是EF中的一个bug。OTOH,OperationContextScope
基于TLS,并且不是Wait
-安全的
一,。同步API维护您的ASP.NET上下文;这包括用户身份和文化等在处理过程中通常很重要的内容。此外,许多ASP.NET API假定它们在实际的ASP.NET上下文上运行(我不是说仅使用HttpContext.Current
;我是说实际假定SynchronizationContext.Current
是AspNetSynchronizationContext
的一个实例)
2-3。我在ASP.NET上下文中直接使用了嵌套,试图在不必重复代码的情况下获取。然而,你不仅失去了