目录
一,引言
二,实例演示
2.1 多线程同步执行下载任务,任务完成后通知
2.2 异步执行下载任务,任务完成后通知
三,async/await的用法
3.1 跨线程修改UI控件
3.2 异步获取数据
一,引言
首先先来区分一下,同步方法,异步方法和多线程:
- 同步方法:调用时需要等待返回结果(相当于阻塞了该线程),才可以继续往下执行业务
- 异步方法:调用时无须等待返回结果(等待时释放线程),可以继续往下执行业务
- 多线程:在主线程之外开启一个新的线程去(增加线程,同步执行)执行业务
关于async/await:
- async和await关键字是C# 5.0时代引入的,它是一种异步编程模型
- 它们本身并不创建新线程,但可以在自行封装的async中利用Task.Run开启新线程
- 方法体中使用await,方法也必须声明为async(成对出现),如果没有await,async关键字也没有意义。
async/await的理解:
个人理解:await可以看着是代码执行的分裂点,当程序执行到await后面的语句时,首先系统会将当前的线程释放(回归线程池,其他程序可调用),并捕获当前上下文(打上标记),进入等待(注意:期间没有线程阻塞)。当await后语句执行完毕,根据标记点调用线程(从线程池随机捕获线程,有概率是原来的线程)执行下面的语句。
二,实例演示
2.1 多线程同步执行下载任务,任务完成后通知
static void Main(string[] args){DownloadHandle();Console.ReadLine();}public static void DownloadHandle(){Console.WriteLine("下载开始!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);var t= Download();Task.WaitAll(t);Console.WriteLine("下载完成!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);}public static Task Download(){return Task.Run(() =>{Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);Console.WriteLine("10%");Console.WriteLine("30%");Console.WriteLine("50%");Console.WriteLine("60%");Console.WriteLine("80%");Console.WriteLine("99%");Console.WriteLine("100%");});}
结果输出:
可以看的,在多线程下载任务时,通过Task.WaitAll(t) 等待线程执行完毕后,主线程一直处于阻塞状态。
2.2 异步执行下载任务,任务完成后通知
static void Main(string[] args){DownloadHandle();Console.ReadLine();}public static async void DownloadHandle(){Console.WriteLine("下载开始!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);await Download();Console.WriteLine("下载完成!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);}public static Task Download(){return Task.Run(() =>{Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);Console.WriteLine("10%");Console.WriteLine("30%");Console.WriteLine("50%");Console.WriteLine("60%");Console.WriteLine("80%");Console.WriteLine("99%");Console.WriteLine("100%");});}
可以看的,在异步下载任务时,主线程未被阻塞。
异步方法async/await的返回值类型一般都是Task或者Task<T>类型的,当返回值为Task时(即方法的返回值类型为void),我们可以直接return Task.Run(()=>{}),而不必await Task.Run(()=>{}),这样也可从一定程度上提高代码执行效率。另外,不推荐使用async 修饰void返回值,会有异常处理方面的问题。
三,async/await的用法
async/await并不能提升代码的执行速度,但可以提高响应能力(吞吐量),即使用异步方式在同一时间可以处理更多的请求。
- 使用同步方式,线程会被耗时操作一直占用,直到耗时操作结束;
- 使用异步方式,程序走到
await
关键字会立即return
,释放线程,剩下的代码将放到一个回调,耗时操作完成时才会回调执行。因此:
- 对于计算密集型工作,使用多线程
- 对于IO密集型工作,采用异步机制
从代码整体的架构设计来说,由于async/await语法容易使得程序被await传染,因此不要从最里面的方法启动线程,而是把启动线程的代码放到最外面,这样一来绝大部分方法就都不再需要用async修饰了,方法就都可以用正常的方式开发了。
3.1 跨线程修改UI控件
通过async/await的机制,可以非常简洁轻松的实现跨线程修改UI控件的问题,也不用使用Invoke。(因为本质上还是在原来的线程上修改的,还没有阻塞UI界面)
private async void button1_Click(object sender, EventArgs e){var t = Task.Run(() => {Thread.Sleep(5000);return "Hello I am TimeConsumingMethod";});textBox1.Text = await t;}
3.2 异步获取数据
在写后端的数据异步处理时,通过async/await语法也可轻易实现,为了防止async/await语法传染,将启动线程的代码放在了最外层,这样在Click事件中就可以正常调用了,不用在增加async关键字了。
private void button2_Click(object sender, EventArgs e){AsyncFunc();}private async Task AsyncFunc(){DataTable dt = await FecthData();this.dataGridView1.DataSource = dt;}private async Task<DataTable> FecthData(){DataTable dt = null;await Task.Run(() =>{dt = new DataTable();dt.Columns.Add("id", typeof(int));dt.Columns.Add("name");for (int i = 0; i < 10000; i++){dt.Rows.Add(new object[] { i, "name" + i.ToString() });}Thread.Sleep(1000);});return dt;}