Sync And Async Delegation
1.进程与线程
进程:计算机概念,程序在运行的时候,记录当前程序对计算机的各种资源的消耗的一种记录,虚拟出来的概念
线程:计算机概念,线程是计算机在执行某个动作时,一个最小的执行流,虚拟出来的概念
包含关系:一个进程包含多个线程
进程和线程的超详细理解
C#中的多线程:Thread是对计算机资源操作的一种封装类
为什么可以有多个线程?
1.多个CPU可以有多个核进行并行计算,计算机具有超强的计算能力,一个线程去操作某一个计算
2.CPU分片,比如1s能处理1000000次计算,可以吧1s内的处理能力再进一步切分,操作系统去调用执行不同的计算
从宏观来讲:就相当于有很多个任务可以去并发执行
从微观来讲:一个物理CPU同一时刻,只能为一个任务服务
2.同步方法和异步方法
同步方法:发起调用,代码按照顺序一行一行地执行,线程ID是同一个,即同一个线程来执行所有的操作
就像是请人吃饭:诚心诚意地请人吃饭,邀请“牧舟”吃饭,可是“牧舟”现在有点忙,那就等他忙完一起去吃
异步方法:发起调用,没有等待完成,直接进入到下一行,发现线程ID不一样,即启动一个新的线程来执行动作
也像是请人吃饭:只是客气一下请人吃饭,邀请“牧舟”吃饭,“牧舟”现在有点忙,那你先忙,我先去吃
异步方法和同步方法的区别
1.异步方法不卡顿界面,UI线程即主线程闲置,计算任务都交给子线程去执行了
同步方法卡顿界面,因为主线程忙于计算,无暇他顾
异步方法改善了用户体验,场景:发送短信、邮件等
2.同步方法慢,异步方法快,因为多线程并发,以资源换性能
/// <summary>
/// 同步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSyncClick(object sender, EventArgs e)
{
//Thread.CurrentThread.ManagedThreadId对标于每个线程的一个ID
Console.WriteLine($"--- btnSyncClick Start 线程ID{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
for(int i = 0; i < 5; i++)
{
string name = string.Format($"btnSyncClick-{i}");
this.DoSomethingLong(name);
}
Console.WriteLine($"--- btnSyncClick End 线程ID{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Console.WriteLine();
}
/// <summary>
/// 异步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsyncClick(object sender, EventArgs e)
{
Console.WriteLine($"-- -- btnAsyncClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Action<string> action = this.DoSomethingLong;
//action.Invoke("btnAsyncClick");
//action.BeginInvoke("btnAsyncClick", null, null);//会分配一个新的线程去执行,.NetCore还不支持
for(int i = 0; i < 5; i++)
{
string name = string.Format($"btnAsyncClick-{i}");
action.BeginInvoke(name, null, null);
}
Console.WriteLine($"-- -- btnAsyncClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Console.WriteLine();
}
/// <summary>
/// 一个比较耗时耗资源的私有方法
/// </summary>
/// <param name="name"></param>
private void DoSomethingLong(string name)
{
Console.WriteLine($"-- -- DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
long lResult = 0;
for(int i = 0; i < 1000_000_000; i++)
{
lResult += i;
}
//Thread.Sleep(200);//不消耗资源,只是等待
Console.WriteLine($"-- -- DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}");
}
3.同步方法按顺序执行,异步方法无序性,由于线程资源是向操作系统申请的,由操作系统的调度策略来决定,所以启动顺序是随机的,执行和结束也是无顺序的
3.委托的异步回调
private void btnAsyncAdvancedClick(object sender, EventArgs e)
{
Console.WriteLine($"-- - btnAsyncAdvancedClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Action<string> action = this.DoSomethingLong;
//通过异步回调委托
//BeginInvoke第一个参数是异步委托所需要的参数
//如这里的参数btnAsyncAdvancedClick就是DoSomethingLong里的name
//BeginInvoke第二个参数是异步委托操作的结果
//BeginInvoke第三个参数是异步委托回调中所需要的参数
//action.BeginInvoke("btnAsyncAdvancedClick", null, null);
//for(int i = 0; i < 3; i++)
//{
// string name = string.Format($"btnAsyncAdvancedClick==={i}");
// action.BeginInvoke(name, null, null);
//}
Console.WriteLine();
//如果想要返回值呢?
Func<string, int> func = s =>
{
Console.WriteLine($"func委托需要的参数:{s}");
return DateTime.Now.Day;
};
//表示异步操作的状态
IAsyncResult asyncResultfunc = null;
IAsyncResult asyncResultaction = null;
//asyncResult = func.BeginInvoke("btnAsyncAdvancedClick", null, null);
//int day = func.EndInvoke(asyncResult);
//Console.WriteLine($"Func委托返回值:{day}");
Console.WriteLine();
//现在有个需求,需要在DoSomethingLong所有计算完成以后,输出一句话“btnAsyncAdvancedClick计算成功了……”
//如果按照同步方法的思路,直接在上述代码下面加一句输出会发现,并不一定是DoSomethingLong所有计算完成以后才会输出
//更多的情况是那句话输出后子线程依然在执行DoSomethingLong方法
Console.WriteLine("同步方法思路: btnAsyncAdvancedClick计算成功了……");
//要实现上述操作,该怎么做?
//就相当于一堆动作执行完毕以后,再执行一点动作
AsyncCallback callbackfunc = ar =>
{
int dayResult = func.EndInvoke(ar);//主线程会等待,func的结果要到这里来拿,必须要执行完毕
//在异步委托的内部,可以通过AsyncState获取到参数
Console.WriteLine($"异步操作完成后调用 btnAsyncAdvancedClick计算成功了…… 回调中需要的参数是:{ar.AsyncState} 今天的日期是:{dayResult}");
};//这叫异步回调,是一个委托
asyncResultfunc = func.BeginInvoke("btnAsyncAdvancedClick", callbackfunc, "牧舟");
AsyncCallback callbackaction = ara =>
{
Console.WriteLine($"action异步操作完成后调用 btnAsyncAdvancedClick计算成功了…… 回调中需要的参数是:{ara.AsyncState}");
};
asyncResultaction = action.BeginInvoke("btnAsyncAdvancedClick", callbackaction, "牧舟");
int j = 0;
while (!asyncResultaction.IsCompleted)//表示前面的动作是否已经完成了
{
if (j < 9)
{
Console.WriteLine($"高级班多线程的第一节课已完成{++j * 10}%......");
}
else
{
Console.WriteLine($"高级班多线程的第一节课已完成99.9999%...");
}
Thread.Sleep(200);
//++j;
}
Console.WriteLine($"-- - btnAsyncAdvancedClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
IAsynResult表示异步操作的状态
IAsynResult asynResult = func.BeginInvoke("btnAsyncAdvancedClick", callbackfunc, "牧舟");
asynResult.AsynWaitHandle.WaitOne();//可以做到等待异步委托执行完毕
asynResult.AsynWaitHandle.WaitOne(-1);//一直等待任务完成
asynResult.AsynWaitHandle.WaitOne(2000);//限时等待,比如这个最多等待2s,过时不候,会阻塞主线程