如何用尽ASP.NET辅助线程以显示异步等待模式的重要性

时间:2018-09-18 05:02:23

标签: c# asynchronous asp.net-web-api async-await

我正在尝试向初级用户展示使用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")}";
    }
}

尽管我试图提出的要点随着同步调用需要花很长时间才能传达出来,但我想知道为什么请求被保留在队列中而不是超时。我正在使用JMeter向我的Web API服务发送250个并发HTTP请求,但是它们从未超时,而是一直等待并完成,尽管存在很大的延迟(〜250秒)。

顺便说一下,在异步版本中,所有响应都将在10秒钟左右返回。

2 个答案:

答案 0 :(得分:-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;
        }
    }
}

用于模拟请求的控制台应用程序(我给了150ms的延迟,以便让一些时间到达等待块):

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();
        }
    }
}

我得到的结果:

首次运行:

Sync used 19 threads in 22412ms
Async used 8 threads in 12911ms

第二次运行:

Sync used 18 threads in 21083ms
Async used 10 threads in 12921ms

第三轮:

Sync used 20 threads in 13578ms
Async used 10 threads in 12899ms

第四轮:

Sync used 18 threads in 21018ms
Async used 5 threads in 12889ms

答案 1 :(得分:-1)

在这里,他们在范例方面做得很出色,这是一个很好的例子。

Shouldn't lesser number of threads be used if I use async?