首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在ASP.NET Web中使用非线程安全的异步/等待API和模式?

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

Stack Overflow用户
提问于 2014-01-08 10:31:33
回答 1查看 14.8K关注 0票数 34

这个问题是由EF数据上下文-异步/等待和多线程触发的。我已经回答了这个问题,但没有提供任何最终的解决方案。

最初的问题是,有很多有用的.NET API(比如Microsoft的DbContext),它们提供了设计用于await的异步方法,但是它们被记录为而不是线程安全的。这使得它们非常适合桌面用户界面应用程序,但不适合服务器端应用程序。编辑的--这可能并不适用于DbContext__,这是微软的https://entityframework.codeplex.com/wikipage?title=Task-based%20Asynchronous%20Pattern%20support%20in%20EF.#ThreadSafety,你自己来判断吧。/EDITED

还有一些已建立的代码模式属于同一类别,例如使用OperationContextScope调用WCF服务代理(询问这里这里),例如:

代码语言:javascript
复制
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一起使用:

代码语言:javascript
复制
// 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开发经验,所以我的问题是:

  1. 在简单的同步调用ASP.NET中的非线程安全API(主要目的是避免牺牲可伸缩性)上使用这种方法有意义吗?
  2. 除了使用同步API调用或根本避免那些APis之外,还有其他方法吗?
  3. 是否有人在ASP.NET MVC或Web项目中使用了类似的东西,并准备好分享他/她的经验?
  4. 任何关于如何使用ASP.NET进行压力测试和分析这种方法的建议都将不胜感激。
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-01-08 12:48:41

实体框架将(应该)处理跨越await点的线程;如果没有,那就是EF中的一个bug。OTOH,OperationContextScope是基于TLS而不是await-safe的。

  1. 同步API维护您的ASP.NET上下文;这包括在处理过程中通常很重要的用户标识和区域性。此外,许多ASP.NET API假设它们运行在实际的ASP.NET上下文上(我并不是说仅仅使用HttpContext.Current;我的意思是实际上假设SynchronizationContext.CurrentAspNetSynchronizationContext的一个实例)。

2-3。我使用了直接嵌套在我自己的单线程上下文上下文中的ASP.NET,以获得MVC子动作工作而不必重复代码。但是,您不仅失去了可伸缩性的好处(至少对于请求的这一部分而言),还会在假设ASP.NET API在ASP.NET上下文上运行的情况下运行它们。

所以,我从来没有在生产中使用过这种方法。我只是在必要时使用了同步API。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20993007

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档