前言:作为一名新手,感觉同步异步与阻塞非阻塞这两对概念是在是长得太像了,网络上也是众说纷纭,接下来给出自己的理解吧,理解仅限于当前的知识范围,可能会有错误,还得继续改进。
话不多说,先贴几个比较高赞的帖子。
理解同步/异步和阻塞/非阻塞的区别_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();
//开启任务1
var ww0 = WaitWait0();
//开启任务0
Debug.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需要耗时1s
Debug.Log("wait Success 0 0");
return 2;
}
int WaitWait1()
{
Debug.Log("will wait 3s 1 1");
Task.Delay(3000).Wait();
//任务1需要耗时3s
Debug.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();
//异步开启任务2
var ww1 = WaitWait3();
//异步开启任务3
Debug.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需要耗时3s
Debug.Log("wait Success 2");
return 2;
}
async Task WaitWait3()
{
Debug.Log("will wait 1s 3");
await Task.Delay(1000);
//任务3需要耗时1s
Debug.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();
//开启任务2
var ww1 =awaitWaitWait3();
//开启任务3
Debug.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需要耗时3s
Debug.Log("wait Success 2");
return 2;
}
async Task WaitWait3()
{
Debug.Log("will wait 1s 3");
await Task.Delay(1000);
//任务3需要耗时1s
Debug.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(一) - 知乎
【编程语言学习笔记C#|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");
}
int ww1Thread = 0;
int ww0Thread = 0;
void MyMain()
{
Debug.Log("begin");
Thread thread1 = new Thread(WaitWait4);
Thread thread2 = new Thread(WaitWait5);
thread1.Start();
//在线程1开启任务1
thread2.Start();
//在线程2开启任务2
Debug.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方便。
文章图片
四、总结 说了那么多,感觉我自己对同步异步跟阻塞非阻塞还是没太弄明白,不过代码总是对的,我们也不必拘泥于某个概念钻牛角尖,还是得以实际需求为主。
推荐阅读
- CAD工程二次开发总结|CAD二次开发--像纬地与CASS程序一样双击桌面图标实现插件的自动挂载(不用netload也不用进入后输入挂载命令)
- Spring|Spring Cloud框架学习-Spring Cloud Sleuth
- Spring|Spring Cloud框架学习-Spring Cloud OpenFeign
- K8S学习笔记0522
- unity|Unity3D中基本GUI控件介绍
- opencv|opencv学习笔记九--背景建模+光流估计
- C#|C# / VB.NET 将Html转为Word
- spring|SSM框架学习——Spring事务
- C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)