这个问题是由EF Data Context - Async/Await & Multithreading个问题引发的.我已经回答了这个问题,但还没有提供任何最终的解决方案.

最初的问题是有很多有用的.NETAPI(比如Microsoft Entity Framework的DbContext),它们提供了设计用于await的异步方法,但是它们的文档记录为not thread-safe.这使得它们非常适合在桌面UI应用程序中使用,但不适用于服务器端应用程序.[EDITED] This might not actually apply to 103, here is Microsoft's 101, judge for yourself. [/EDITED]

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

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

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

问题的根源是异步ASP中使用的AspNetSynchronizationContext.NET页面,用ASP.NET线程池中较少的线程来完成更多HTTP请求.对于AspNetSynchronizationContextawait延续可以在发起异步操作的线程的不同线程上排队,而原始线程被释放到池中,并可用于服务另一个HTTP请求.This substantially improves the server-side code scalability.《必读》It's All About the SynchronizationContext中详细介绍了该机制.因此,尽管涉及there is no concurrent API access,但潜在的线程切换仍然阻止我们使用上述API.

I've been thinking about how to solve this without sacrificing the scalability.显然,对于可能受线程切换影响的异步调用的范围,将这些API恢复到maintain thread affinity是唯一的方法.

让我们假设我们有这样的线程亲和力.这些调用中的大多数无论如何都是IO绑定的(There Is No Thread).当异步任务处于挂起状态时,发起该任务的线程可以用来为另一个类似任务的后续任务提供服务,该任务的结果已经可用.因此,它应该不会对可伸缩性造成太大影响.这种做法并不是什么 fresh 事,实际上是a similar single-threaded model is 101.IMO,这是使Node.js如此流行的原因之一.

我不明白为什么不能在ASP.NET上下文中使用此方法.自定义任务调度器(我们称其为ThreadAffinityTaskScheduler)可能维护一个单独的"关联单元"线程池,以进一步提高可伸缩性.一旦任务排入其中一个"单元"线程,任务内的所有await个延续都将在同一个线程上进行.

下面是如何将the linked question中的非线程安全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的优秀StaTaskScheduler为基础,获得了100 as a proof of concept分.ThreadAffinityTaskScheduler维护的池线程不是典型COM意义上的STA线程,但它们确实实现了await个延续的线程关联(SingleThreadSynchronizationContext负责).

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

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

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

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

  4. 关于如何使用ASP对这种方法进行压力测试和分析的任何建议.净是

推荐答案

实体框架将(应该)很好地处理await点的线程 skip ;如果没有,那就是EF中的一个bug.OTOH,OperationContextScope基于TLS,不安全.

1.同步API维护您的ASP.网络语境;这包括用户身份和文化等在处理过程中通常很重要的事情.还有一些ASP.NET API假定它们运行在实际的ASP.NET上下文(我不是说只使用HttpContext.Current;我是说实际上假设SynchronizationContext.CurrentAspNetSynchronizationContext的一个实例).

2-3. 我使用了my own single-threaded context个直接嵌套在ASP.NET环境下,在不需要重复代码的情况下try 获取async MVC child actions working.然而,不仅会失go 可伸缩性优势(至少对于请求的这一部分),还会遇到ASP.NET API假定它们运行在ASP.NET上下文.

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

Asp.net相关问答推荐

从组件属性调用异步方法的正确方法

为什么 @Html.EditorFor 和 @Html.PasswordFor 在 MVC 中创建不同的样式框?

在 Web.Config 中模拟标签

如何在运行时判断动态数据类型的类型?

Azure 自定义控制器/API .Net 后端

在 appSettings 中存储字符串数组?

在 RedirectToAction 调用中传播 QueryString 参数

如何使用 ASP.NET Identity 创建用户并获取新创建的 ID

HttpContext.Current.Request.IsAuthenticated 和 HttpContext.Current.User.Identity.IsAuthenticated 有什么区别?

在 Asp.net Rowcommand 事件中获取行索引

如何解决 Kerberos 双跳问题?

如何设置默认页面 asp.net

发布网站项目时临时路径太长

Owin.IAppBuilder不包含MapSignalR的定义

如何防止 XXE 攻击(.NET 中的 XmlDocument)

对 ASP.NET 2.0 网页进行单元测试的最佳方法是什么?

MVC3 和实体框架

ASP.NET:在 Response.Redirect(...) 之后代码会发生什么?

ExecuteReader:连接属性尚未初始化

MVC4 - 当优化设置为 true 时Bundle 不起作用