写点什么

一个例子形象地理解同步与异步

作者:EquatorCoco
  • 2024-01-23
    福建
  • 本文字数:2379 字

    阅读完需:约 8 分钟

请看一个示例:

同步方式请求接口


请求一次接口耗时大约 100 多毫秒


代码


一个 for 循环,循环 500 次,调用方法 Request,Request 方法中一个 while(true)无限循环,同步方式请求 url 获取数据。代码点评:要是写一个 while(true)没问题,这是想运行 500 个 while(true),这代码是错误的,行不通。应该使用 Thread 或者 Task.Run 加 TaskCreationOptions.LongRunning 参数。这当然是有问题的代码,请看下面运行截图,只有第一个 while(true)在执行,其它的 499 个 while(true)根本没有执行机会。


static int num = 0;static ConcurrentDictionary<int, object> dict = new ConcurrentDictionary<int, object>();
static void Main(string[] args){ CalcSpeed();
for (int i = 0; i < 500; i++) { Request(i); }
Console.WriteLine($"Main函数结束"); Console.ReadLine();}
static void Request(int index){ dict.TryAdd(index, null);
while (true) { string url = "http://localhost:5028/Test/TestGet"; string result = HttpUtil.HttpGet(url); Interlocked.Increment(ref num); }}
static void CalcSpeed(){ _ = Task.Factory.StartNew(() => { Stopwatch sw = Stopwatch.StartNew(); while (true) { Thread.Sleep(2000); double speed = num / sw.Elapsed.TotalSeconds; ThreadPool.GetMaxThreads(out int w1, out int c1); ThreadPool.GetAvailableThreads(out int w2, out int c2); Console.WriteLine($"有 {dict.Count.ToString().PadLeft(3)} 个 while(true) 在执行,线程池活动线程数:{(w1 - w2).ToString().PadRight(3)} 速度:{speed:#### ####.0} 次/秒"); } }, TaskCreationOptions.LongRunning);}
复制代码


运行截图



说明


代码中没有创建线程,也没有使用 Task.Run,请求一次接口耗时大约 100 多毫秒,while(true)在主线程中执行,平均 1 秒请求接口不到 10 次。注意:只有第一个 while(true)在执行。


修改 1:在 Request 函数中添加一行代码 Thread.Sleep(1);


代码


static void Request(int index){    dict.TryAdd(index, null);
while (true) { string url = "http://localhost:5028/Test/TestGet"; string result = HttpUtil.HttpGet(url); Interlocked.Increment(ref num);
Thread.Sleep(1); }}
复制代码


运行截图



说明


没什么用,速度还变慢了一点。依然是有问题的代码。依然只有第一个 while(true)在执行。


修改 2:在 Request 函数中添加一行代码 await Task.Delay(1);


VS 自动在 void Request 前面添加了 async 关键字


代码


static async void Request(int index){    dict.TryAdd(index, null);
while (true) { string url = "http://localhost:5028/Test/TestGet"; string result = HttpUtil.HttpGet(url); Interlocked.Increment(ref num);
await Task.Delay(1); }}
复制代码


运行截图



说明


速度快多了,并且越来越快。有多个 while(true)在执行,并且在执行的 while(true)数量越来越多,最终会达到 500 个。这是比较神奇的地方,仅仅加了一行 await Task.Delay(1);同步方法 Request 就变成了异步方法。在执行 await Task.Delay(1);这一行时,其它 while(true)得到了执行机会,你们可以验证一下。同步请求分别在不同的线程中执行,你们可以打印线程 ID 验证一下。


修改 3:前面使用的是 HttpUtil.HttpGet 同步请求,修改为异步请求,await Task.Delay(1);这一行也不需要了


代码


static async void Request(int index){    dict.TryAdd(index, null);
while (true) { string url = "http://localhost:5028/Test/TestGet"; var httpClient = HttpClientFactory.GetClient(); string result = await (await httpClient.GetAsync(url)).Content.ReadAsStringAsync(); Interlocked.Increment(ref num); }}
复制代码


运行截图



说明


速度非常快。异步的优势体现出来了。


修改 4:有没有人会认为修改 2,把同步代码用 Task.Run 包一下,速度会更快?


代码


static async void Request(int index){    dict.TryAdd(index, null);
while (true) { await Task.Run(() => { string url = "http://localhost:5028/Test/TestGet"; string result = HttpUtil.HttpGet(url); Interlocked.Increment(ref num); });
await Task.Delay(1); }}
复制代码


运行截图



说明


线程饥饿,全部阻塞,没有返回结果,速度是 0。


总结


通过这个例子形象地体会一下同步与异步,以及为什么要使用异步。如果你写的代码是异步的,但是调用的 IO 接口又是同步的,这比真正的异步效率要差很多,但比同步代码有所提升。针对修改 2,有人会说,这代码有问题,后面的 while(true)会延迟好久才会执行。但是如果 for 循环的数量是少量的,程序启动时的一点延迟是允许的,就没有问题,修改代码如下:


for (int i = 0; i < 20; i++){    Request(i);}
复制代码


运行截图:



说明:20 个 while(true)都在运行,比一个 while(true)要快很多。当然,没必要这么写了,直接 new 20 个 Thread 就可以。但如果 for 循环就是 500 次,而且需要调用的 IO 接口又是同步的,那么就老老实实写 500 个 new Thread。如果非要用异步,设置一下线程池的大小,大于 500,避免线程饥饿。


ThreadPool.SetMinThreads(800, 800);ThreadPool.SetMinThreads(600, 600);
复制代码


你会发现,不能想当然,依然有问题,这时强行用异步就很容易写出 BUG 了。最后,再好好体会一下上面的例子。


文章转载自:0611163

原文链接:https://www.cnblogs.com/s0611163/p/17979998

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
一个例子形象地理解同步与异步_Java_EquatorCoco_InfoQ写作社区