经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
一个例子形象的理解同步与异步
来源:cnblogs  作者:0611163  时间:2024/1/22 17:11:32  对本文有异议

请看一个示例:

同步方式请求接口

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

代码

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

  1. static int num = 0;
  2. static ConcurrentDictionary<int, object> dict = new ConcurrentDictionary<int, object>();
  3. static void Main(string[] args)
  4. {
  5. CalcSpeed();
  6. for (int i = 0; i < 500; i++)
  7. {
  8. Reuest(i);
  9. }
  10. Console.WriteLine($"Main函数结束");
  11. Console.ReadLine();
  12. }
  13. static void Reuest(int index)
  14. {
  15. dict.TryAdd(index, null);
  16. while (true)
  17. {
  18. string url = "http://localhost:5028/Test/TestGet";
  19. string result = HttpUtil.HttpGet(url);
  20. Interlocked.Increment(ref num);
  21. }
  22. }
  23. static void CalcSpeed()
  24. {
  25. _ = Task.Factory.StartNew(() =>
  26. {
  27. Stopwatch sw = Stopwatch.StartNew();
  28. while (true)
  29. {
  30. Thread.Sleep(2000);
  31. double speed = num / sw.Elapsed.TotalSeconds;
  32. ThreadPool.GetMaxThreads(out int w1, out int c1);
  33. ThreadPool.GetAvailableThreads(out int w2, out int c2);
  34. Console.WriteLine($"有 {dict.Count.ToString().PadLeft(3)} 个 while(true) 在执行,线程池活动线程数:{(w1 - w2).ToString().PadRight(3)} 速度:{speed:#### ####.0} 次/秒");
  35. }
  36. }, TaskCreationOptions.LongRunning);
  37. }

运行截图

说明

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

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

代码

  1. static void Reuest(int index)
  2. {
  3. dict.TryAdd(index, null);
  4. while (true)
  5. {
  6. string url = "http://localhost:5028/Test/TestGet";
  7. string result = HttpUtil.HttpGet(url);
  8. Interlocked.Increment(ref num);
  9. Thread.Sleep(1);
  10. }
  11. }

运行截图

说明

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

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

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

代码

  1. static async void Reuest(int index)
  2. {
  3. dict.TryAdd(index, null);
  4. while (true)
  5. {
  6. string url = "http://localhost:5028/Test/TestGet";
  7. string result = HttpUtil.HttpGet(url);
  8. Interlocked.Increment(ref num);
  9. await Task.Delay(1);
  10. }
  11. }

运行截图

说明

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

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

代码

  1. static async void Reuest(int index)
  2. {
  3. dict.TryAdd(index, null);
  4. while (true)
  5. {
  6. string url = "http://localhost:5028/Test/TestGet";
  7. var httpClient = HttpClientFactory.GetClient();
  8. string result = await (await httpClient.GetAsync(url)).Content.ReadAsStringAsync();
  9. Interlocked.Increment(ref num);
  10. }
  11. }

运行截图

说明

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

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

代码

  1. static async void Reuest(int index)
  2. {
  3. dict.TryAdd(index, null);
  4. while (true)
  5. {
  6. await Task.Run(() =>
  7. {
  8. string url = "http://localhost:5028/Test/TestGet";
  9. string result = HttpUtil.HttpGet(url);
  10. Interlocked.Increment(ref num);
  11. });
  12. await Task.Delay(1);
  13. }
  14. }

运行截图

说明

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

总结

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

  1. for (int i = 0; i < 20; i++)
  2. {
  3. Reuest(i);
  4. }

运行截图:

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

  1. ThreadPool.SetMinThreads(800, 800);
  2. ThreadPool.SetMinThreads(600, 600);

你会发现,依然有问题,

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号