理解概念
进程:占用资源的最小单元(相当于工作车间)
线程:调度运算的最小单元(相当于车间内的工人)
1.在使用winform窗体程序时,碰到耗时操作经常会导致窗体假死的情况;
那怎么解决UI线程的假死问题呢?
可以使用异步关键字async和await将阻塞的线程释放出来,解决窗体在操作耗时函数的时候造成的假死问题。
异步方法async可以修饰的返回值类型
void
Task:表示返回的是一个异步任务
Task<T>:表示返回的是一个返回值为T类型的异步任务
await 等待的是什么?
对应上述三种分别是void Task 带返回值的Task
那Task是什么?
Task表示异步操作,可以理解为在另一个线程执行任务,而不阻塞当前线程;
如何使用Task?
调用Start()方法后会在另一个线程中立即执行DoJob()函数中的内容,同时也会在主线程中往下执行MessageBox.Show函数,直到程序向下执行碰到t.Wait或者t.Result方法阻塞主线程,在这里等待Task的异步任务处理完成再往下执行。
//方法一 Task<string> t = new Task<string>(() => { return DoJob(); }); t.Start(); MessageBox.Show("继续干活"); t.Wait();//阻塞主线程,等待任务完成
DoJob()就是一个返回值为string的普通函数;
这里使用构造函数建立的异步任务,参数是一个Action委托,里面放需要异步执行的工作,注意需要执行t.Start();才能执行任务;
//方法二 Task<string> tf = Task.Factory.StartNew(() => { return DoJob(); }); MessageBox.Show("继续干活,在线程:" + Task.CurrentId); tf.Wait();//阻塞主线程,等待任务完成
这里使用工厂模式直接执行DoJob任务
//方法三 Task<string> t = Task.Run(() => { return DoJob(); }); MessageBox.Show("继续干活"); string str = t.Result;//阻塞主线程,等待任务完成
使用Task的Run方法也是直接执行DoJob任务
由于上述的三种方法均使用了 .Result 或者 .Wait这两个会阻塞主线程的方法,都会使得UI界面假死,因此就需要使用async和await关键字将线程释放出来。
2.async和await关键字的使用情况:
主线程执行代码:
Task<string> tas = DoJobAsync();//已经开始执行任务 this.button1.Text = "正在后台处理数据..."; MessageBox.Show("继续干活");
自己编写的DoJobAsync异步方法的代码
//自己编写的异步方法 private Task<string> DoJobAsync() { Task<string> t = Task.Run(() => { //需要异步执行的耗时工作量 for (int i = 0; i < 1000; i++) { Thread.Sleep(5);//模拟耗时操作 Console.WriteLine("AnotherJob IN Thread:" + Thread.CurrentThread.ManagedThreadId + " Job ExeTimes: " + i.ToString()); } MessageBox.Show("异步任务Success"); return "任务完成"; }); return t; }
主线程在执行
Task<string> tas = DoJobAsync();
这段代码后会立即向下执行这段代码,而且DoJobAsync的工作是交给了另一个线程进行处理;
this.button1.Text = "正在后台处理数据...";
如果需要拿到DoJobAsync的返回值怎么办呢?这里的返回值是string
使用
string str = await tas;
这里使用await 关键字修饰,方法就必须使用async关键字修饰,否则就不是异步方法。
因此方法体内使用了await关键字,那么改方法就必须用async修饰,例如Button Click事件就必须要改写为
private async void button1_Click(object sender, EventArgs e) { string str = await DoJobAsync(); }
这样的方式是不会阻止主线程,例如UI线程,不会陷入假死状态。
await 时是释放线程,线程能去执行其它任务;.Result 和 .Wait 就是让线程暂停,等待结果。
3.如何监控异步任务的执行进度?
通过IProgress<T>, progress只提供了一个方法void Report(T value),通过Report方法把一个T类型的值报告给IProgress,然后IProgress<in T>的实现类Progress<in T>的构造函数接收类型为Action<T>的形参,通过这个委托让进度显示在UI界面中。
实际就是干活的异步方法必须要有一个IProgress<T>参数,然后通过Report(T value)函数将进度反馈出去;
实现类怎么构建呢?
Progress<in T>通过构造函数生成,参数的类型是Action<T>的委托,这个委托可以把进度细节暴露到干活的异步方法之外,就实现了例如将进度写到UI线程上。
代码:
//主线程调用函数 private async Task Display() { //当前线程 Progress<int> progress = new Progress<int>(percent => { this.progressBar1.Value = percent;//在当前线程修改控件,因此不会抛出异常 Console.WriteLine("{0}%", percent); }); //线程池线程 //await Task.Run(() => DoProcessing(progress)); await DoJobAsync(progress); Console.WriteLine(""); Console.WriteLine("结束"); }
//自己编写的异步方法 private Task<string> DoJobAsync(IProgress<int> progress) { Task<string> t = Task.Run(() => { //需要异步执行的耗时工作量 for (int i = 0; i < 1000; i++) { if (progress != null) { progress.Report(i*100/1000);//返回进度执行情况 } Thread.Sleep(5);//模拟耗时操作 Console.WriteLine("AnotherJob IN Thread:" + Thread.CurrentThread.ManagedThreadId + " Job ExeTimes: " + i.ToString()); } MessageBox.Show("异步任务Success"); return "任务完成"; }); return t; }
主函数直接在Button Click事件中调用即可。
//显示进度 Task ts = Display(); await ts;
End