为啥我的异步 ASP.NET Web API 控制器阻塞了主线程?

Posted

技术标签:

【中文标题】为啥我的异步 ASP.NET Web API 控制器阻塞了主线程?【英文标题】:Why is my async ASP.NET Web API controller blocking the main thread?为什么我的异步 ASP.NET Web API 控制器阻塞了主线程? 【发布时间】:2013-02-06 10:31:46 【问题描述】:

我有一个 ASP.NET Web API 控制器,我会认为会异步操作。控制器设计为第一个请求休眠 20 秒,但立即处理任何后续请求。所以我预期的时间表是这样的:

    提出请求 1。 提出请求 2。 提出请求 3。 请求 2 返回。 请求 3 返回。 等待约 20 秒。 请求 1 返回。

相反,no 请求会在请求 1 完成之前返回。

我可以确认(基于调试输出),入口线程和休眠线程 ID 不同。我故意使用TaskCreationOptions.LongRunning 将睡眠强制到一个单独的线程上,但应用程序仍然拒绝为任何新请求提供服务,直到睡眠完成。

我是否遗漏了有关异步 Web API 控制器如何真正工作的基本知识?


public class ValuesController : ApiController

    private static bool _firstTime = true;

    public async Task<string> Get()
    
        Debug.WriteLine("Entry thread id: 0. Sync: 1",
            Thread.CurrentThread.ManagedThreadId,
            SynchronizationContext.Current);
        await LongWaitAsync();
        return "FOOBAR";
    

    private Task LongWaitAsync()
    
        return Task.Factory.StartNew(() =>
            
                if (_firstTime)
                
                    _firstTime = false;
                    Debug.WriteLine("Sleepy thread id: 0. Sync: 1",
                        Thread.CurrentThread.ManagedThreadId,
                        SynchronizationContext.Current);
                    Thread.Sleep(20000);
                    Debug.WriteLine("Finished sleeping");
                
            ,
            CancellationToken.None,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default);
    

【问题讨论】:

第一步,我将_firstTime 标记为volatile,以确保不同的线程可以看到它一致地变化。 您使用什么网络服务器来托管? _firstTime 标记为volatile 没有区别。我尝试在 Win 7 Pro 上托管 IIS Express 8.0 和 IIS 7.5。 @Snixtor - 这可能没什么区别,但无论如何都应该这样做。 @Damien_The_Unbeliever 适当指出。我承认我必须查找它的含义,它肯定与我的示例代码相关。 【参考方案1】:

这实际上与服务器无关,而与客户端有关。 Chrome 和 Firefox 似乎都不想发送他们认为是“重复”的请求,直到第一个请求得到响应。任一浏览器的单独“私人”会话将立即从第二个请求返回。 Internet Explorer 9 似乎没有表现出这种行为。

为了与客户端实现隔离,我整理了以下客户端。

class Program

    static void Main(string[] args)
    
        var t1 = Task.Run(() => FetchData(1));
        var t2 = Task.Run(() => FetchData(2));
        var t3 = Task.Run(() => FetchData(3));

        var index = Task.WaitAny(t1, t2, t3);
        Console.WriteLine("Task 0 finished first", index + 1);

        Task.WaitAll(t1, t2, t3);
        Console.WriteLine("All tasks have finished");

        Console.WriteLine("Press any key");
        Console.ReadKey(true);
    

    static void FetchData(int clientNumber)
    
        var client = new WebClient();
        string data = client.DownloadString("http://localhost:61852/api/values");
        Console.WriteLine("Client 0 got data: 1", clientNumber, data);
    

它的输出是:

    客户端 2 获得数据:“FOOBAR”(在启动的毫秒内) 客户端 3 获得数据:“FOOBAR” 任务 2 先完成 (在这里等很久) 客户端 1 获得数据:“FOOBAR” 所有任务都已完成

【讨论】:

以为我快疯了。谢谢你的启示!【参考方案2】:

在我的例子中,这里是输出(它切换到另一个线程,从 5 到 10):

Entry thread id: 5. Sync: System.Web.LegacyAspNetSynchronizationContext
Sleepy thread id: 10. Sync: 
Finished sleeping
The thread '<No Name>' (0x2c84) has exited with code 0 (0x0).
Entry thread id: 7. Sync: System.Web.LegacyAspNetSynchronizationContext
The thread '<No Name>' (0x1818) has exited with code 0 (0x0).
Entry thread id: 5. Sync: System.Web.LegacyAspNetSynchronizationContext
The thread '<No Name>' (0xd4c) has exited with code 0 (0x0).
The thread '<No Name>' (0x2c30) has exited with code 0 (0x0).

这可能是由于环境和机器运行(单核)使运行时决定如何运行它。

【讨论】:

以上是关于为啥我的异步 ASP.NET Web API 控制器阻塞了主线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 ASP.NET Core Web API 操作异步执行?

为啥 Asp.net web api 控制器不从 IController 派生

为啥当包含控制器动作的数据时日期时间值会发生变化?使用 AngularJs 和 Asp.Net Web API 2

为啥从 ASP.NET Core Web API 的 ControllerBase 与 Controller 派生?

ASP.NET MVC4 异步控制器 - 为啥要使用?

C# EF6 使用 Unity 对一个上下文进行多次异步调用 - Asp.Net Web Api