C#异步编程

  在使用多线程编写端口扫描程序时,我本身感受同步和肯定全部线程都执行完的时间是2个比较麻烦的问题。有园友评论说如今已经不手动建立thread对象了,而是直接使用Task异步方式,个人网络编程老师也讲到了异步编程的优越性。在学习了课本上的知识后,进行了一个总结分享给你们。从.NET4.5开始,用async和await关键字再加上Task.Run是一个很是不错的异步编程模型。编程

1.await和async服务器

  异步模式从技术上看就是利用委托来实现的,它的主要好处是在异步执行的过程当中,用户仍然能够操控UI界面。使用Task类和使用Thread类有不少类似的地方,Task类也是经过调用方法去实现一个任务的完成,方法但是是命名方法或匿名方法,在执行过程当中可以使用async和await来实现异步执行。async是一个修饰符,它只能用在方法或者事件处理程序的签名中。对于方法可分为有返回值和无返回值两种状况,事件则只有一种,以下面三条语句所示:网络

    private async Task<int> MethodAsync();//有返回值的异步方法多线程

    private async Task MethodAsync();//无返回值的异步方法异步

    private async void btnOk_Click();//异步事件处理程序async

  await是一个运算符,它表示等待异步执行的结果。也能够理解为await运算符其实是对方法的返回值进行操做,也就是对Task<Result>进行操做,而不是对方法自己进行操做。还有一点要注意,await是必定要放在异步方法的内部,若是没有放在内部的话,VS会自动报错。如下是async和await使用的例子: 异步编程

    private async void button5_Click(object sender, EventArgs e)
    {
      Task a = Method1Async();
      //此处可继续执行其余代码
      await a;//等待任务a完成
      Task<int> b = Method2Async();
      //此处可继续执行其余代码
      int c = await b;//等待任务b完成,且能够拿到任务b的返回值
    }函数

    Task Method1Async();
    async Task<int> Method2Async()
    {
      await Task.Delay(100);
      return 1;
    }学习

  await和同步编程最大的不一样之处是:异步等待任务完成的时候,在不会继续执行后面的代码时,也不会影响界面的操做。在.NET提供的类中,异步方法都是约定用Async做为后缀,这样能够很清楚的知道这个方法是异步方法仍是同步方法。spa

2. 建立任务

  建立任务也就是将任务与要执行的方法联系起来,编写任务执行的方法时,这个方法既能够是同步方法也能够是异步方法,还能够是匿名方法。执行异步方法时,必须用async和Task共同表示没有返回值的任务,用async和Task<TResult>共同表示返回值为TResult的任务。如下是定义执行任务的方法。

    private async void button5_Click(object sender, EventArgs e)
    {

      //Task.Run方法表示使用默认的任务调度程序在线程池中经过后台执行指定的任务

      //若是不须要本身去调度方法,使用这个方式最方便
      await Task.Run(()=>Method1Async());//执行同步方法
      int c = await Task.Run(()=>Method2Async());//执行异步方法
      await Task.Run(async () => { c = 2; });//执行异步匿名方法
    }
    void Method1Async();
    async Task<int> Method2Async(){...}

  Task.Run方法经常使用的重载形式有如下4种,另外它也是能够用new关键字显示建立任务,可是这种方式用的很少。

    Task Run(Func<Task> function);//执行不带返回值的任务

    Task<TResult> Run<TResult>(Func<Task<TResult>> function);//执行带返回值的任务

    Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);//执行过程当中能够监听取消通知

    Task Run(Func<Task> function, CancellationToken cancellationToken);//执行过程当中能够监听取消通知

3. 终止任务

  在执行任务时确定会出现须要终止任务的状况,这里的终止告诉任务你要尽快停下来再也不执行了,而不是直接销毁任务实例。这里能够打个比方,学生一块儿出去吃饭了,学生与老师都在班群里面,忽然班群里老师说要让同窗们集合,若是全部同窗都看到这个消息,而后学生们开始出发,这样就能够正确的集合。上面的例子有一个很重要的前提,那就是全部同窗都要看到这个消息,也就是学生是时刻监听消息的。CancellationTokenSource类和CancellationToken结构用于实现多线程、线程池和Task任务的取消操做,处理模式与上面的例子类似。建立的班群就是CancellationTokenSource对象,收到的通知就是CancellationToken对象。CancellationTokenSource用于建立取消通知,CancellationToken则用于传播应取消操做的通知,当调用任务前,能够先建立取消源对象CancellationTokenSource cts=new CancellationTokenSource();,若是但愿在30秒后自动发出取消通知,能够传入参数CancellationTokenSource(TimeSpan.FromSeconds(30));CancellationToken ct=cts.Token;,后一句代码是拿到取消的通知。CancellationTokenSource还有一个Cancel方法,将这个属性设为true时,该方法会将全部添加了取消标记的CancellationToken对象的IsCancellationRequested属性都设置为true,这样取消通知就传递到了正在执行的任务。

  任务收到取消通知后,能够选择两种方式来终止操做。第一种方式是简单的从委托返回。这种实现方式相似于在调用任务的代码中一个bool值来表示取消通知,任务收到后就直接返回了。当采用这种方式时任务状态的返回值为TaskStatus.RanToCompletion枚举值,它表示正常完成,而不是TaskStatus.Canceled枚举值。第二种方式是在代码里引起OperationCanceledException异常,并将其传递到在其上请求了取消的标记,采用这种方式取消的任务会转换为用Canceled枚举值表示的状态。完成引起异常的首选方式是调用ct.ThrowIfCancellationRequestes();。如下是代码示例,写了一个winform程序,利用进度条来取消任务。第一个图是没有引起异常时,程序退出for循环,执行后面的代码后返回了,第二张图是第二种方式,引起了异常后直接跳转到catch语句块了。

     CancellationTokenSource cts; private async void button3_Click(object sender, EventArgs e) { progressBar1.Maximum = 100; progressBar1.Value = 0; cts = new CancellationTokenSource(); var aa = MYThreadAsync("a", cts.Token); try { await aa; listBox1.Items.Add("await后面"); } catch { if (aa.IsCanceled) listBox1.Items.Add("a取消"); } } private void button4_Click(object sender, EventArgs e) { cts.Cancel(); } public async Task MYThreadAsync(string s, CancellationToken ct) { for (int i = 0; i < 50; i++) { if (ct.IsCancellationRequested) break;          //点击关闭按钮,IsCancellationRequested就为true,就会退出for循环,这是第一种方式
                progressBar1.Value += 2; await Task.Delay(100); ct.ThrowIfCancellationRequested();//这是第二种方式,它会终止任务而且返回catch语句块里面
 } listBox1.Items.Add("任务" + s + "完成了"); }

4. 获取任务执行的状态

  在异步编程中,很显然任务执行的状态是一个很是重要的参数。在任务的生命周期里,能够经过Status属性来获取任务执行的状态,当任务完成后还能够经过任务属性知道任务完成的状况。可利用任务实例的Status属性获取任务执行的状态,任务执行的状态用TaskStatus枚举表示,如下是TaskStatus的枚举值:

Created:任务已经初始化,但还没有进入调度计划

WaitingForActivation:该任务已进入调度计划,正在等待被调度程序激活

WaitingToRun:该任务已被调度程序激活,但还没有开始执行

Running:该任务正在运行,但还没有完成

RanToCompletion:该任务已经成功完成

Canceled:该任务因为被取消而完成,引起异常或调用方已向该任务的CancellationToken发出信号

Faulted:该任务由于出现未经处理的异常而完成

WaitingForChildrenToComplete:该任务自己已完成,正等待附加的子任务完成

  任务完成状况相关的属性有IsCompleted、IsCanceled和IsFaulted等属性,从单词意思上看不难理解它们的意思,其中要注意IsCompleted属性表示任务是否完成,不管是正常结束仍是由于取消或异常而完成都为完成。

5. 任务执行的进度

  有时候咱们但愿让某些异步操做提供进度通知,以便在界面中显示异步操做执行的进度,能够用Progress<T>类来获得任务执行的进度。如下是利用方法里的Report方法将方法内变量的值传回建立任务的事件代码里,从而更新进度条的值。

     CancellationTokenSource cts; private async void button3_Click(object sender, EventArgs e) { progressBar1.Maximum = 100; progressBar1.Value = 0; cts = new CancellationTokenSource(); CancellationToken ct = cts.Token; var pp = new Progress<int>(); pp.ProgressChanged += (s, n) => { progressBar1.Value = n; }; var tt = Task.Run(()=>MYThreadAsync(pp,cts.Token,500),cts.Token); try { await tt; if (tt.Exception == null) listBox1.Items.Add("任务完成"); } catch (Exception ex) { listBox1.Items.Add("异常" + ex.Message); } } private void button4_Click(object sender, EventArgs e) { cts.Cancel(); } public  void MYThreadAsync(IProgress<int> progress, CancellationToken ct, int delay) { int p = 0;//进度
            while (p < 100 && ct.IsCancellationRequested == false) { p += 1; Thread.Sleep(delay); progress.Report(p);//这个方法将会触发ProgressChanged事件更新进度条
 } }

6. 定时完成任务

  不管是服务器仍是客户端,都是有定时完成某个任务的须要的。System.Timers.Timer类是一个不错的定时设置类,这个类能够引起事件,但它默认是在线程池中引起事件,而不是在当前线程中引起事件。Timer类的经常使用属性有AutoReset和Interval属性,AutoReset是获取或设置一个bool值,该值为true表示每次间隔结束时都引起一次Elapsed事件,false表示仅在首次间隔结束时引起一次该事件。Interval属性是获取或设置两次Elapsed事件的间隔时间,该值必须大于零并小于Int.MaxValue,默认值为100毫秒。Timer类还有两个经常使用方法那就是Start和Stop方法。

  还有一个System.Threading.Timer类,它也是在线程池中定时执行任务,它与前一个Timer类的区别是该类不使用事件模型,而是直接经过TimerCallback类型的委托来实现的。该类的构造函数为:Timer(TimerCallback callback,Object state,TimeSpan douTime,TimeSpan period)。callback表示要执行的方法,state表示一个包含回调方法要使用的信息的对象,dueTime是首次调用回调方法以前延迟的时间,period表示每次调用回调方法的时间间隔,-1表示终止。这样建立对象后,首次到达dueTime延时时间会自动调用一次callback委托,之后每隔period时间间隔调用一次。如下是这两种方式的运行效果和源代码。

 

     System.Timers.Timer timer; System.Threading.Timer threadtimer; private void button2_Click(object sender, EventArgs e)//Timers.Timer { progressBar1.Maximum = 100; progressBar1.Value = 0; int pro=0; timer = new System.Timers.Timer(500); timer.AutoReset = true; timer.Elapsed+= (obj, args) => { pro+=5; progressBar1.Value = pro; }; timer.Start(); } private void button5_Click(object sender, EventArgs e) { timer.Stop(); listBox1.Items.Add("第一个已经中止"); }     //Threading.Timer类 private void button1_Click(object sender, EventArgs e) { progressBar2.Maximum = 100; progressBar2.Value = 0; TimeSpan dueTime = new TimeSpan(0, 0, 0, 1); TimeSpan period = new TimeSpan(0, 0, 0, 0, 200); System.Threading.TimerCallback timecall = new TimerCallback((obj) => progressBar2.Value += 5); threadtimer = new System.Threading.Timer(timecall, null, dueTime, period); } private void button6_Click(object sender, EventArgs e) { threadtimer.Dispose(); listBox1.Items.Add("第二个已经中止"); }

  这篇文章只总结了单个任务的异步执行的基础,还得继续学习多任务并行执行。若是有更好的技术或者与企业使用相关的异步技术,但愿园友能够提出我继续学习。

相关文章
相关标签/搜索