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


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

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

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

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

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

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

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

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

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

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


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

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

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

using System.Collections;using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using System.Threading;public class ProgressBar : MonoBehaviour{void Start(){MyMain();Debug.Log("Start OK,Continue Next Work");}void MyMain()//假如MyMain是个任务{Debug.Log("begin");//任务调用开启var ww1 = WaitWait1();//开启任务1var ww0 = WaitWait0();//开启任务0Debug.Log("end");//任务调用结束Debug.Log(ww0);//通知wwo当前值Debug.Log(ww1);//通知ww1当前值Debug.Log("Function End");//函数终止任务}int WaitWait0(){Debug.Log("will wait 1s 0 0");Task.Delay(1000).Wait();//任务0需要耗时1sDebug.Log("wait Success 0 0");return 2;}int WaitWait1(){Debug.Log("will wait 3s 1 1");Task.Delay(3000).Wait();//任务1需要耗时3sDebug.Log("wait Success 1 1");return 3;}}

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

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

 二、异步但阻塞的例子?

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

using System.Collections;using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using System.Threading;public class ProgressBar : MonoBehaviour{void Start(){MyMain();Debug.Log("Start OK,Continue Next Work");}async Task MyMain(){Debug.Log("begin");//任务调用开启var ww0 = WaitWait2();//异步开启任务2var ww1 = WaitWait3();//异步开启任务3Debug.Log("end");//任务调用结束Debug.Log(await ww0);//异步通知wwo当前值Debug.Log(await ww1);//异步通知ww1当前值Debug.Log("Function End");//函数终止任务}async Task WaitWait2(){Debug.Log("will wait 3s 2");await Task.Delay(3000);//任务2需要耗时3sDebug.Log("wait Success 2");return 2;}async Task WaitWait3(){Debug.Log("will wait 1s 3");await Task.Delay(1000);//任务3需要耗时1sDebug.Log("wait Success 3");return 3;}}

还是看执行结果来分析:在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的位置改一改。

using System.Collections;using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using System.Threading;public class ProgressBar : MonoBehaviour{void Start(){MyMain();Debug.Log("Start OK,Continue Next Work");}async Task MyMain(){Debug.Log("begin");//任务调用开启var ww0 =await WaitWait2();//开启任务2var ww1 =awaitWaitWait3();//开启任务3Debug.Log("end");//任务调用结束Debug.Log(ww0);//顺序通知wwo当前值Debug.Log(ww1);//顺序通知ww1当前值Debug.Log("Function End");//函数终止任务}async Task WaitWait2(){Debug.Log("will wait 3s 2");await Task.Delay(3000);//任务2需要耗时3sDebug.Log("wait Success 2");return 2;}async Task WaitWait3(){Debug.Log("will wait 1s 3");await Task.Delay(1000);//任务3需要耗时1sDebug.Log("wait Success 3");return 3;}}

看下面结果,这个时候相对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(一) – 知乎

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

using System.Collections;using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using System.Threading;public class ProgressBar : MonoBehaviour{void Start(){MyMain();Debug.Log("Start OK,Continue Next Work");}int ww1Thread = 0;int ww0Thread = 0;void MyMain(){Debug.Log("begin");Thread thread1 = new Thread(WaitWait4); Thread thread2 = new Thread(WaitWait5);thread1.Start();//在线程1开启任务1thread2.Start();//在线程2开启任务2Debug.Log("end");Debug.Log(ww1Thread);//通知ww1Thread当前值Debug.Log(ww0Thread);//通知ww0Thread当前值Debug.Log("Function End");//函数终止任务}void WaitWait4(){Debug.Log("will wait 1s 4");Task.Delay(1000).Wait();Debug.Log("wait Success 4");ww0Thread = 2;Debug.Log(ww0Thread);//回调通知ww0Thread当前值}void WaitWait5(){Debug.Log("will wait 3s 5");Task.Delay(3000).Wait();Debug.Log("wait Success 5");ww1Thread = 3;Debug.Log(ww1Thread);//回调通知ww1Thread当前值}}

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

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

四、总结

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