如何在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请求。使用AspNetSynchronizationContextawait可以在与发起异步操作的线程不同的线程上排队一个延续,同时将原始线程释放到池中,并可以用于服务另一个HTTP请求。

该机制在必须阅读的“关于同步上下文的全部内容”中进行了详细描述。因此,尽管 涉及

,但是潜在的线程切换仍然阻止我们使用上述API。

显然,收回这些API的唯一方法是针对可能受到线程切换影响的异步调用范围

假设我们具有这种线程相似性。无论如何,这些调用中的大多数都是受IO约束的(没有线程)。当异步任务挂起时,它所源自的线程可用于服务另一个类似任务的延续,该结果已经可用。因此,它不应过多地损害可伸缩性。这种方法并不是什么新鲜事物,实际上,

。IMO,这是使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 Toub的出色表现,我继续进行并

StaTaskSchedulerThreadAffinityTaskScheduler在经典的COM意义上,由其维护的池线程不是STA线程,但是它们确实实现了线程的await连续性(SingleThreadSynchronizationContext对此负责)。

到目前为止,我已经将该代码作为控制台应用程序进行了测试,并且看起来可以按设计工作。我尚未在ASP.NET页面中对其进行测试。我没有大量的ASP.NET生产开发经验,所以我的问题是:

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

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

  3. 有没有人在ASP.NET MVC或Web API项目中使用过类似的东西,并准备分享他/她的经验?

  4. 任何有关如何使用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上下文中的我自己的单线程上下文,以尝试使asyncMVC子操作起作用而不必重复代码。但是,不仅会失去可伸缩性的好处(至少对于请求的那部分而言),而且假设ASP.NET

API在ASP.NET上下文中运行,您还会遇到它们。

因此,我从未在生产中使用这种方法。我只是在必要时使用了同步API。

以上是 如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式? 的全部内容, 来源链接: utcz.com/qa/398597.html

回到顶部