C# 如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式?

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服务代理,例如: 使用(

这个问题是由以下因素引发的。我已经回答了这个问题,但还没有提供任何最终的解决方案

最初的问题是有很多有用的.NET API(如Microsoft Entity Framework),它们提供了异步方法,设计用于
等待
,但它们被记录为非线程安全。这使得它们非常适合在桌面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开发经验,因此我的问题是:

  • 在ASP.NET中对非线程安全API的简单同步调用上使用这种方法是否有意义(主要目的是避免牺牲可伸缩性)

  • 除了使用同步API调用或完全避免这些API之外,还有其他方法吗

  • 有没有人在ASP.NET MVC或Web API项目中使用过类似的东西,并准备好分享自己的经验

  • 任何关于如何使用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上下文中直接使用了嵌套,试图在不必重复代码的情况下获取。然而,你不仅失去了