如何在 ASP.NET Web API 中使用非线程安全的 async/await API 和模式? [英] How to use non-thread-safe async/await APIs and patterns with ASP.NET Web API?

查看:26
本文介绍了如何在 ASP.NET Web API 中使用非线程安全的 async/await API 和模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题已由 EF Data Context - Async/Await &多线程.我已经回答了这个问题,但没有提供任何最终解决方案.

This question has been triggered by EF Data Context - Async/Await & Multithreading. I've answered that one, but haven't provided any ultimate solution.

最初的问题是那里有很多有用的 .NET API(例如 Microsoft Entity Framework 的 DbContext),它提供设计用于与 await 一起使用的异步方法,但它们被记录为不是线程安全的.这使得它们非常适合在桌面 UI 应用程序中使用,但不适用于服务器端应用程序.[已编辑] 这实际上可能不适用于 DbContext,这是 Microsoft 的 关于EF6线程安全的声明,自己判断吧. [/EDITED]

The original problem is that there are a lot of useful .NET APIs out there (like Microsoft Entity Framework's DbContext), which provide asynchronous methods designed to be used with await, yet they are documented as not thread-safe. That makes them great for use in desktop UI apps, but not for server-side apps. This might not actually apply to DbContext, here is Microsoft's statement on EF6 thread safety, judge for yourself. [/EDITED]

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

There are also some established code patterns falling into the same category, like calling a WCF service proxy with OperationContextScope (asked here and here), e.g.:

using (var docClient = CreateDocumentServiceClient())
using (new OperationContextScope(docClient.InnerChannel))
{
    return await docClient.GetDocumentAsync(docId);
}

这可能会失败,因为 OperationContextScope 在其实现中使用了线程本地存储.

This may fail because OperationContextScope uses thread local storage in its implementation.

问题的根源是 AspNetSynchronizationContext,它用于异步 ASP.NET 页面,以使用来自 ASP.NET 线程池的更少线程来满足更多 HTTP 请求.使用 AspNetSynchronizationContextawait 延续可以在与启动异步操作的线程不同的线程上排队,而原始线程被释放到池中并可用于服务另一个 HTTP 请求.这大大提高了服务器端代码的可伸缩性.It's All About the SynchronizationContext,必读.因此,虽然不涉及并发 API 访问,但潜在的线程切换仍然会阻止我们使用上述 API.

The source of the problem is AspNetSynchronizationContext which is used in asynchronous ASP.NET pages to fulfill more HTTP requests with less threads from ASP.NET thread pool. With AspNetSynchronizationContext, an await continuation can be queued on a different thread from the one which initiated the async operation, while the original thread is released to the pool and can be used to serve another HTTP request. This substantially improves the server-side code scalability. The mechanism is described in great details in It's All About the SynchronizationContext, a must-read. So, while there is no concurrent API access involved, a potential thread switch still prevents us from using the aforementioned APIs.

我一直在考虑如何在不牺牲可扩展性的情况下解决这个问题.显然,恢复这些 API 的唯一方法是为范围保持线程亲和性的异步调用可能受线程切换影响.

I've been thinking about how to solve this without sacrificing the scalability. Apparently, the only way to have those APIs back is to maintain thread affinity for the scope of the async calls potentially affected by a thread switch.

假设我们有这样的线程亲和力.无论如何,这些调用中的大多数都是 IO 绑定的(没有线程).当一个异步任务挂起时,它发起的线程可用于为另一个类似任务的延续提供服务,该结果已经可用.因此,它不应该过多地损害可扩展性.这种做法并不是什么新鲜事,其实类似的单线程模型已经被Node.js成功使用.IMO,这是让 Node.js 如此受欢迎的原因之一.

Let's say we have such thread affinity. Most of those calls are IO-bound anyway (There Is No Thread). While an async task is pending, the thread it's been originated on can be used to serve a continuation of another similar task, which result is already available. Thus, it shouldn't hurt scalability too much. This approach is nothing new, in fact, a similar single-threaded model is successfully used by Node.js. IMO, this is one of those things that make Node.js so popular.

我不明白为什么不能在 ASP.NET 上下文中使用这种方法.自定义任务调度程序(我们称之为 ThreadAffinityTaskScheduler)可能会维护一个单独的亲和单元"线程池,以进一步提高可伸缩性.一旦任务排队到这些公寓"线程之一,任务内的所有 await 延续都将在同一个线程上进行.

I don't see why this approach could not be used in ASP.NET context. A custom task scheduler (let's call it ThreadAffinityTaskScheduler) might maintain a separate pool of "affinity apartment" threads, to improve scalability even further. Once the task has been queued to one of those "apartment" threads, all await continuations inside the task will be taking place on the very same thread.

以下是来自 链接问题 的非线程安全 API 如何与此类 ThreadAffinityTaskScheduler<一起使用/代码>:

Here's how a non-thread-safe API from the linked question might be used with such 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);

我继续实现了ThreadAffinityTaskScheduler 作为概念证明,基于 Stephen Toub 的优秀 StaTaskScheduler.ThreadAffinityTaskScheduler 维护的池线程不是经典 COM 意义上的 STA 线程,但它们确实实现了 await 延续的线程亲和性(SingleThreadSynchronizationContext 负责为此).

I went ahead and implemented ThreadAffinityTaskScheduler as a proof of concept, based on the Stephen Toub's excellent StaTaskScheduler. The pool threads maintained by ThreadAffinityTaskScheduler are not STA thread in the classic COM sense, but they do implement thread affinity for await continuations (SingleThreadSynchronizationContext is responsible for that).

到目前为止,我已经将此代码作为控制台应用程序进行了测试,它似乎可以按设计工作.我还没有在 ASP.NET 页面中对其进行测试.我没有很多生产 ASP.NET 开发经验,所以我的问题是:

So far, I've tested this code as console app and it appears to work as designed. I haven't tested it inside an ASP.NET page yet. I don't have a lot of production ASP.NET development experience, so my questions are:

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

  1. Does it make sense to use this approach over simple synchronous invocation of non-thread-safe APIs in ASP.NET (the main goal is to avoid sacrificing scalability)?

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

Is there alternative approaches, besides using synchronous API invocations or avoiding those APis at all?

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

Has anyone used something similar in ASP.NET MVC or Web API projects and is ready to share his/her experience?

关于如何使用 ASP.NET 对这种方法进行压力测试和分析的任何建议都是感激不尽.

Any advice on how to stress-test and profile this approach with ASP.NET would be appreciated.

推荐答案

Entity Framework 将(应该)处理跨 await 点的线程跳转就好了;如果不是,那么这是 EF 中的一个错误.OTOH,OperationContextScope 基于 TLS,不是 await-安全的.

Entity Framework will (should) handle thread jumps across await points just fine; if it doesn't, then that's a bug in EF. OTOH, OperationContextScope is based on TLS and is not await-safe.

1.同步 API 维护您的 ASP.NET 上下文;这包括在处理过程中通常很重要的用户身份和文化等内容.此外,许多 ASP.NET API 假设它们在实际的 ASP.NET 上下文中运行(我的意思不是只使用 HttpContext.Current;我的意思是实际上假设 SynchronizationContext.CurrentAspNetSynchronizationContext 的一个实例.

1. Synchronous APIs maintain your ASP.NET context; this includes things such as user identity and culture that are often important during processing. Also, a number of ASP.NET APIs assume they are running on an actual ASP.NET context (I don't mean just using HttpContext.Current; I mean actually assuming that SynchronizationContext.Current is an instance of AspNetSynchronizationContext).

2-3.我使用 我自己的单线程上下文 直接嵌套在 ASP.NET 上下文中,试图让 async MVC 子操作工作 而不必复制代码.但是,您不仅失去了可扩展性优势(至少对于请求的那部分),而且假设它们在 ASP.NET 上下文中运行,您还会遇到 ASP.NET API.

2-3. I have used my own single-threaded context nested directly within the ASP.NET context, in attempts to get async MVC child actions working without having to duplicate code. However, not only do you lose the scalability benefits (for that part of the request, at least), you also run into the ASP.NET APIs assuming that they're running on an ASP.NET context.

所以,我从未在生产中使用过这种方法.我只是在必要时使用同步 API.

So, I have never used this approach in production. I just end up using the synchronous APIs when necessary.

这篇关于如何在 ASP.NET Web API 中使用非线程安全的 async/await API 和模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆