如何耗尽ASP.NET工作线程以显示异步等待模式的重要性

use*_*111 7 c# asynchronous async-await asp.net-web-api

我试图向大三学生展示异步编程的重要性,使用asyncawait.出于此目的,我创建了一个带有一个控制器和两个GET操作的ASP.NET Web API项目.一个GET操作是同步的,另一个是异步的.我想证明,在同步阻塞i/o调用的情况下,所有可用的ASP.NET工作线程都在等待并且没有任何用处,同时当更多请求到达时,它们将超时,因为所有可用的线程都是等待I/O线程完成.问题是,我的下面的代码片段部分传达了这一点.它可以在异步调用的情况下正常工作,但不能用于同步调用.如果我取消注释已注释掉的代码行,它就不会发生,ASP.NET运行时可以处理更多的线程.代码段如下:

public class TestController : ApiController
{        
    // -> Uncommenting the below method proves my point of scalability <-
    //public async Task<string> Get()
    //{
    //    CodeHolder obj = new CodeHolder();
    //    return await obj.AsyncData();
    //}
    // -> Uncommenting the below method doesn't enforce time outs, rather waits <-
    public string Get()
    {
        CodeHolder obj = new CodeHolder();
        return obj.SyncData();
    }        
}
class CodeHolder
{
    public string SyncData()
    {
        Task.Delay(10000).Wait();
        return $"I am returned from Sync after waiting for 10 second at {DateTime.Now.ToString("HH:mm:ss:fffffff")}";
    }
    public async Task<string> AsyncData()
    {
        await System.Threading.Tasks.Task.Delay(10000);
        return $"I am returned from Async after semi-waiting for 10 second at {DateTime.Now.ToString("HH:mm:ss:fffffff")}";
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然我试图提出这一点,但是随着同步调用需要很长时间才能完成,但我想知道为什么请求被保留在队列中而不是超时.我正在使用JMeter向我的Web API服务发送250个并发HTTP请求,但它们永远不会超时而是等待并完成,尽管延迟非常大(约250秒).

顺便说一句,在异步版本中,所有响应都在大约10秒内返回.

小智 -1

您可以证明同步版本比异步版本使用线程池中更多的线程。

这就是我想出的演示 Web 控制器的方法:

[Route("api/v1/[controller]")]
public class ThreadController : Controller
{
    private static readonly object locker = new object();

    private static HashSet<int> syncIds = new HashSet<int>();

    [HttpGet("sync")]
    public ActionResult<string> GetSync()
    {
        var id = Thread.CurrentThread.ManagedThreadId;

        lock (locker)
        {
            syncIds.Add(id);
        }

        Task.Delay(10000).Wait();

        return $"I am returned from Sync after waiting for 10 second at {DateTime.Now.ToString("HH:mm:ss:fffffff")}";
    }

    [HttpGet("sync/count")]
    public ActionResult<int> CountSync()
    {
        lock (locker)
        {
            int count = syncIds.Count;

            syncIds.Clear();

            return count;
        }
    }

    private static HashSet<int> asyncIds = new HashSet<int>();

    [HttpGet("async")]
    public async Task<ActionResult<string>> GetAsync()
    {
        var id = Thread.CurrentThread.ManagedThreadId;

        lock (locker)
        {
            asyncIds.Add(id);
        }

        await Task.Delay(10000);

        return $"I am returned from Async after waiting for 10 second at {DateTime.Now.ToString("HH:mm:ss:fffffff")}";
    }

    [HttpGet("async/count")]
    public ActionResult<int> CountAsync()
    {
        lock (locker)
        {
            int count = asyncIds.Count;

            asyncIds.Clear();

            return count;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一个控制台应用程序来模拟请求(我给出了 150 毫秒的延迟,以便有时间到达等待块):

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var http = new HttpClient() )
            {
                var stopwatch = Stopwatch.StartNew();

                var sync = new List<Task>();

                for (int i = 0; i < 20; i++)
                {
                    sync.Add(http.GetAsync("http://localhost:5000/api/v1/thread/sync") );

                    Thread.Sleep(150);
                }

                Task.WaitAll(sync.ToArray() );

                stopwatch.Stop();

                Console.WriteLine("Sync used " + http.GetAsync("http://localhost:5000/api/v1/thread/sync/count").Result.Content.ReadAsStringAsync().Result + " threads in " + stopwatch.ElapsedMilliseconds + "ms");

                stopwatch.Restart();

                var async = new List<Task>();

                for (int i = 0; i < 20; i++)
                {
                    async.Add(http.GetAsync("http://localhost:5000/api/v1/thread/async") );

                    Thread.Sleep(150);
                }

                Task.WaitAll(async.ToArray() );

                stopwatch.Stop();

                Console.WriteLine("Async used " + http.GetAsync("http://localhost:5000/api/v1/thread/async/count").Result.Content.ReadAsStringAsync().Result + " threads in " + stopwatch.ElapsedMilliseconds + "ms");
            }

            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我得到的结果:

第一次运行:

Sync used 19 threads in 22412ms
Async used 8 threads in 12911ms
Run Code Online (Sandbox Code Playgroud)

第二次运行:

Sync used 18 threads in 21083ms
Async used 10 threads in 12921ms
Run Code Online (Sandbox Code Playgroud)

第三次运行:

Sync used 20 threads in 13578ms
Async used 10 threads in 12899ms
Run Code Online (Sandbox Code Playgroud)

第四次运行:

Sync used 18 threads in 21018ms
Async used 5 threads in 12889ms
Run Code Online (Sandbox Code Playgroud)