我正在尝试向初级用户展示使用async
和await
进行异步编程的重要性。为此,我创建了一个带有一个控制器和两个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秒钟左右返回。
答案 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)
在这里,他们在范例方面做得很出色,这是一个很好的例子。