当前位置:   article > 正文

Unity C#:浅析同步异步与阻塞非阻塞与async关键字_unity 同步与异步

unity 同步与异步

前言:作为一名新手,感觉同步异步与阻塞非阻塞这两对概念是在是长得太像了,网络上也是众说纷纭,接下来给出自己的理解吧,理解仅限于当前的知识范围,可能会有错误,还得继续改进。


话不多说,先贴几个比较高赞的帖子。

理解同步/异步和阻塞/非阻塞的区别_linhuaiyang的博客-CSDN博客_异步阻塞和同步阻塞的区别

怎样理解阻塞非阻塞与同步异步的区别? - 知乎

socket阻塞和非阻塞有哪些影响_mayue_csdn的博客-CSDN博客_非阻塞socket

完全理解同步/异步与阻塞/非阻塞 - 知乎

IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)_historyasamirror的博客-CSDN博客_同步io和异步io的区别

同步/异步,阻塞/非阻塞概念深度解析_萧萧九宸的博客-CSDN博客_同步和异步阻塞和非阻塞

微软官方举的异步的例子(举得复杂了,容易让人找不到本质):C# 中的异步编程 | Microsoft Docs

跟其他例子:【转载】C#之异步 - 走看看

关于C#异步编程你应该了解的几点建议


最后。总结上述材料,我感觉同步异步就是更强调消息的通知,而阻塞非阻塞更强调任务的执行,一般来说非阻塞就是异步的,但异步不一定非阻塞,而阻塞和同步则是同义词。当然,消息的通知和任务的执行取决于我们是怎么定义的。讲的有点复杂,还是举例子理解理解。

注意:以下程序是在unity中运行的,会用到unity的生命周期。并且会用到C#的async关键字,需要读者补一补。

一、既是同步又是阻塞的例子

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Threading.Tasks;
  4. using UnityEngine;
  5. using System.Threading;
  6. public class ProgressBar : MonoBehaviour
  7. {
  8. void Start()
  9. {
  10. MyMain();
  11. Debug.Log("Start OK,Continue Next Work");
  12. }
  13. void MyMain()//假如MyMain是个任务
  14. {
  15. Debug.Log("begin");//任务调用开启
  16. var ww1 = WaitWait1();//开启任务1
  17. var ww0 = WaitWait0();//开启任务0
  18. Debug.Log("end");//任务调用结束
  19. Debug.Log(ww0);//通知wwo当前值
  20. Debug.Log(ww1);//通知ww1当前值
  21. Debug.Log("Function End");//函数终止任务
  22. }
  23. int WaitWait0()
  24. {
  25. Debug.Log("will wait 1s 0 0");
  26. Task.Delay(1000).Wait();//任务0需要耗时1s
  27. Debug.Log("wait Success 0 0");
  28. return 2;
  29. }
  30. int WaitWait1()
  31. {
  32. Debug.Log("will wait 3s 1 1");
  33. Task.Delay(3000).Wait();//任务1需要耗时3s
  34. Debug.Log("wait Success 1 1");
  35. return 3;
  36. }
  37. }

毫无疑问,上面的代码是一句一句执行的,并且能很明显感受到启动游戏的时候,卡了那么一会。这说明了Start函数在等执行MyMain都执行完才结束(耗时4s),才进行Update开启游戏运行。而MyMain得等WaitWait1和WaitWait0这两任务按顺序执行完再按顺序挨个继续通知ww0和ww1的当前值。这不就是又阻塞又同步吗。

看截图要注意看时间点:这几个数据是要敏感的,21-18=3秒,22-21=1秒。整个过程耗时4秒

二、同步非阻塞

最近做核酸想出来的。一个医务人员扫描身份证,一个做鼻拭子。把受检者当成信号,那么受检者是按顺序的,先扫身份证在做鼻拭子,因此是同步的。但是这两个医务人员是不受影响的,一个医务人员扫描身份证的时候并没有阻塞住做鼻拭子的医务人员去做鼻拭子。因此这个是同步非阻塞的。

但是,当扫描身份证的医务人员速度比较慢,做鼻拭子的医务人员没事干了,必须要等到扫描完身份证的人过来才能进行下一步。这时候同步非阻塞就变成同步阻塞了。

三、异步但阻塞的例子?

因为我也不太清楚是不是阻塞还是非阻塞的,所以加了个?,个人暂且认为他是阻塞的。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Threading.Tasks;
  4. using UnityEngine;
  5. using System.Threading;
  6. public class ProgressBar : MonoBehaviour
  7. {
  8. void Start()
  9. {
  10. MyMain();
  11. Debug.Log("Start OK,Continue Next Work");
  12. }
  13. async Task MyMain()
  14. {
  15. Debug.Log("begin");//任务调用开启
  16. var ww0 = WaitWait2();//异步开启任务2
  17. var ww1 = WaitWait3();//异步开启任务3
  18. Debug.Log("end");//任务调用结束
  19. Debug.Log(await ww0);//异步通知wwo当前值
  20. Debug.Log(await ww1);//异步通知ww1当前值
  21. Debug.Log("Function End");//函数终止任务
  22. }
  23. async Task<int> WaitWait2()
  24. {
  25. Debug.Log("will wait 3s 2");
  26. await Task.Delay(3000);//任务2需要耗时3s
  27. Debug.Log("wait Success 2");
  28. return 2;
  29. }
  30. async Task<int> WaitWait3()
  31. {
  32. Debug.Log("will wait 1s 3");
  33. await Task.Delay(1000);//任务3需要耗时1s
  34. Debug.Log("wait Success 3");
  35. return 3;
  36. }
  37. }

还是看执行结果来分析:在MyMain没执行完就开始继续Start后的代码了,这说明相对Start函数,MyMain是异步非阻塞的。

接下来分析MyMain里面的。在MyMain里面,WaitWait2和WaitWait3显然是异步开启的,尽管是WaitWait2先开启,但是并不阻塞WaitWait3的执行,并且WaitWait3先提前结束任务,用时1s,WaitWait3用时3s后结束任务。在整个流程中,WaitWait3与WaitWait2是并行异步的,整个MyMain任务用时3s。

但为什么说这个是阻塞的呢?因为相对Debug.Log("Function End")这个函数终止的任务来说,他是需要等到两个异步的通知都收到了才进行的,因此我认为他在这是阻塞的。(并且注意通知ww0和ww1的当前值也是按顺序同步阻塞的)

 然后,为了对这个await理解更深刻点,我们把上面的await的位置改一改。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Threading.Tasks;
  4. using UnityEngine;
  5. using System.Threading;
  6. public class ProgressBar : MonoBehaviour
  7. {
  8. void Start()
  9. {
  10. MyMain();
  11. Debug.Log("Start OK,Continue Next Work");
  12. }
  13. async Task MyMain()
  14. {
  15. Debug.Log("begin");//任务调用开启
  16. var ww0 =await WaitWait2();//开启任务2
  17. var ww1 =await WaitWait3();//开启任务3
  18. Debug.Log("end");//任务调用结束
  19. Debug.Log(ww0);//顺序通知wwo当前值
  20. Debug.Log(ww1);//顺序通知ww1当前值
  21. Debug.Log("Function End");//函数终止任务
  22. }
  23. async Task<int> WaitWait2()
  24. {
  25. Debug.Log("will wait 3s 2");
  26. await Task.Delay(3000);//任务2需要耗时3s
  27. Debug.Log("wait Success 2");
  28. return 2;
  29. }
  30. async Task<int> WaitWait3()
  31. {
  32. Debug.Log("will wait 1s 3");
  33. await Task.Delay(1000);//任务3需要耗时1s
  34. Debug.Log("wait Success 3");
  35. return 3;
  36. }
  37. }

看下面结果,这个时候相对Start还是异步非阻塞的,但是对WaitWait2和WaitWait3来说却是同步阻塞的,WaitWait3需要等WaitWait2都运行完才会开启,因此整个MyMain任务用时4秒。

从这也可以看出await具有局部阻塞的作用,在程序的某个位置加await关键字,则会阻塞,需要等待任务完成才进行下一步。但是使用await是不会使MyMain在Start中阻塞,仅是局部阻塞。

四、异步非阻塞

我想了想,要想做到异步非阻塞,只有用异步回调或者多线程了。并且C#中实现异步的task是由线程池实现的,不过C#帮我们封装好了,能让我们很好地掌控他,具体可看微软官方文档:Task表示一个可以返回值的异步操作。https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task-1?view=net-6.0

关于task还可以参考:c#异步编程-Task(一) - 知乎

接下来用多线程来举例异步非阻塞吧,这样可能理解起来会更舒服:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Threading.Tasks;
  4. using UnityEngine;
  5. using System.Threading;
  6. public class ProgressBar : MonoBehaviour
  7. {
  8. void Start()
  9. {
  10. MyMain();
  11. Debug.Log("Start OK,Continue Next Work");
  12. }
  13. int ww1Thread = 0;
  14. int ww0Thread = 0;
  15. void MyMain()
  16. {
  17. Debug.Log("begin");
  18. Thread thread1 = new Thread(WaitWait4);
  19. Thread thread2 = new Thread(WaitWait5);
  20. thread1.Start();//在线程1开启任务1
  21. thread2.Start();//在线程2开启任务2
  22. Debug.Log("end");
  23. Debug.Log(ww1Thread);//通知ww1Thread当前值
  24. Debug.Log(ww0Thread);//通知ww0Thread当前值
  25. Debug.Log("Function End");//函数终止任务
  26. }
  27. void WaitWait4()
  28. {
  29. Debug.Log("will wait 1s 4");
  30. Task.Delay(1000).Wait();
  31. Debug.Log("wait Success 4");
  32. ww0Thread = 2;
  33. Debug.Log(ww0Thread);//回调通知ww0Thread当前值
  34. }
  35. void WaitWait5()
  36. {
  37. Debug.Log("will wait 3s 5");
  38. Task.Delay(3000).Wait();
  39. Debug.Log("wait Success 5");
  40. ww1Thread = 3;
  41. Debug.Log(ww1Thread);//回调通知ww1Thread当前值
  42. }
  43. }

这个程序就很顺畅了,所有的全都是并行的,Debug.Log("Function End")这个任务也不用等WaitWait4和WaitWait5任务执行完就可以执行了。但是缺点就是为了解决阻塞Debug.Log("Function End"),需要将通知放到线程中进行回调。

最终整个任务的运行时间也是3秒,这样想想确实不如直接用task方便。

五、总结

说了那么多,感觉我自己对同步异步跟阻塞非阻塞还是没太弄明白,不过代码总是对的,我们也不必拘泥于某个概念钻牛角尖,还是得以实际需求为主。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/104476?site
推荐阅读
相关标签
  

闽ICP备14008679号