该文章仅为个人理解,如有错误请指正,标红内容为重点,会有针对异步的多个文章,包含Task,APM(BeginInvoke,EndInvoke)等。
一、基本概念:
二、Async/await概念解释:
Async:表示函数包含一个或多个基于任务(Task)的异步方法调用。
返回值:有返回值,修改为Task
await:
对每个任务调用使用await,await之后的代码编译器都会当做先前任务的延续。
当遇到await后会立刻跳出当前方法,直到await中task执行完毕后,await之后代码会自动加入延续。
编译器会为await生成的代码获取当前同步上下文,并创建任务调度器,然后把调度器赋值给延续。
等价于Task.ContinueWith(Action,Task.FromCurrentSynchronizationContext)代码。
也可以将await理解为一个运算符,该运算符表示等待异步执行的结果,也就是对Task.Result属性进行操作,而不是对方法本身进行操作。
三、基本使用:
//情况1.异步事件处理程序
private async void btnAsync1_Click(object sender, EventArgs e)
{
//开启基于Task的异步方法调用
string taskResult=await Task.Run(() =>
{
Thread.Sleep(1000);
return "BtnAsync1";
});
//延续,await会自动捕获同步上下文(UI线程的同步上下文),不存在跨线程操作Control
btnAsync1.Text = taskResult;
}
//情况2.没有返回值,修改为Task
private void btnAsync2_Click(object sender, EventArgs e)
{
//必须进行接收返回值,因为要捕获异常和Task运行结果
Task taskResult = SetBtnTxt();
btnAsync2.Text = "BtnAsync2";
}
//情况2.没有返回值,修改为Task
public async Task SetBtnTxt()
{
string taskResult = await Task.Run(() =>
{
Thread.Sleep(1000);
return "BtnAsync2";
});
btnAsync2.Text = taskResult;
}
//情况3.返回值为string,修改为Task
private void btnAsync3_Click(object sender, EventArgs e)
{
Task taskResult = GetBtnText();
btnAsync3.Text = "BtnAsync3";
}
//情况3.返回值为string,修改为Task
public async Task GetBtnText()
{
string taskResult= await Task.Run(() =>
{
Thread.Sleep(1000);
return "BtnAsync3";
});
btnAsync3.Text = taskResult;
return taskResult;
}
四、Async/await和死锁:
情况:
//发生死锁
private void btnAsync4_Click(object sender, EventArgs e)
{
Task taskResult = GetBtnText();
//1.出现线程阻塞
btnAsync4.Text = taskResult.Result;
}
public async Task GetBtnText()
{
string taskResult=await Task.Run(() =>
{
Thread.Sleep(1000);
return "BtnAsync3";
});
//2.出现线程阻塞
return taskResult;
}
白话说明:
当代码执跳出GetBtnText后,会执行taskResult.Result会发生UI线程阻塞,而GetBtnText中执行完Task代码后,会由await进行延续Task后的代码,延续代码会由Task中的线程封送到UI线程。
Task的返回值,在执行完延续代码后才会进行返回,延续代码需要UI线程,而UI线程在等待Task的返回值,导致了UI线程和Task线程之间相互等待,发生死锁。
只要使用了await之后,即使没有返回值,也没有延续代码,也会发生延续操作,也必须调用UI线程。
专业术语说明:
使用async和await模式时,就引入了非阻塞执行流程(异步Task)到应用程序中,非阻流程可以启动许多异步任务,在时间上单个线程(UI线程)的执行流程是顺序的,当允许它持续贯穿执行整个应用程序时,非阻塞流程(Task线程)才能达到最佳状态,代码最终释放关联的线程到线程池去执行其他任务,当在线程(UI线程)上初始化非阻塞执行流程、通过等待任务(Task.Result)来显示阻塞线程时,就会阻止非阻塞流程完成(Task线程需要封送延续任务到UI线程上)。
关联的线程无法完成,直到等待的任务完成,如果应用程序是多线程的,那么当显式阻塞线城市,会导致不必要的系统资源消耗,最终会影响应用程序的总体响应能力。如果应用程序是单线程时(或者具备线程关联性),则在使用async和await启动非阻塞执行流程后选择显式阻塞执行流程时,会产生死锁,因为阻塞了唯一可以继续执行任务的线程。
解决方式:
1.在所有异步交互里使用一致性技术,即Demo中btnAsync4_Click继续使用async/await。
2.使用Task.ConfigureAwait()控制是否让编译器为await生成代码,以捕获上下文。在TaskAsync中Task.Run()函数增加.ConfigureAwait(false),最后为Task.Run().ConfigureAwait(false)。