初探异步编程(1)

理解概念

进程:占用资源的最小单元(相当于工作车间)

线程:调度运算的最小单元(相当于车间内的工人)

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

原文地址:https://www.cnblogs.com/LeeSki/p/12200062.html