• C#多线程编程(3)开启子任务


    上一篇我讲解了await和async关键字,这两个关键字的作用是将async限定的方法中await关键字后面的部分封装成一个委托,该委托会在await修饰的Task完成后再执行。简单的说,就是等待任务完成后,后面的程序才执行,且该等待不会造成线程阻塞。关键是在任务执行完成后,程序会继续交给主线程执行。接下来,我来介绍在任务执行结束后,用新任务来执行方法。

    废话不多上,上代码,我们来看看如何在任务结束后继续由线程池继续完成其他方法。

     1 static void Main(string[] args)
     2 {
     3     RunAsync();
     4     Console.WriteLine("Async Run");
     5     Console.Read();
     6 }
     7 public static void RunAsync()
     8 {
     9     var task = Task.Run(() =>
    10     {
    11         Thread.Sleep(2000);
    12         return "task finished";
    13     });
    14     //当task完成后,会在由线程池继续执行
    15     task.ContinueWith(t =>
    16     {
    17         Thread.Sleep(2000);
    18         Console.WriteLine(task.Result);
    19     });
    20 }

    可以看到,RunAsync方法中并没有添加async和await关键字,但是运行程序后,主线程没有被阻塞。这是因为ContinueWith会将委托参数“挂”到线程池的任务队列中,该委托只有在task执行结束后,才会开始执行。ContinueWith方法会返回一个Task,该Task就是task执行结束后会开始执行的Task,该Task也是可以等待的。注意,ContinueWIth和await关键字的区别就在:await在任务完成后,会由主线程继续执行,但是ContinueWith中的方法会继续由线程池执行。

    接下来,我们来了解一下,任务能否取消,以及取消后会发生什么。

    C#提供的任务取消的方式是“协作式”取消,在构造任务时,需要先构造一个System.Threading.CancellationTaskSource对象,该对象看起来像这样

    1  public sealed class CancellationTaskSource:IDisposable{
    2     public CancellationTokenSource();
    3     public void Dispose();//释放资源
    4     public bool IsCancellationRequested{get;}
    5     public CancellationToken Token{get;}  
    6     public void Cancel();//内部调用Cancel并传递false
    7     public void Cancel(bool throwOnFirstException);
    8 }

    CancellationTaskSource对象中含有一个Token,它的类型是CancellationToken,这是一个轻量级值类型,它包含一个私有的对CancellationTaskSource的引用,当构造Task时,需要将这个Token传入,如下所示

    static void Main(string[] args){
        CancelTask();
        Console.WriteLine("Async Run");
        Console.Read();
    }
    public static void CancelTask()
    {
        //定时1000ms后自动取消任务
        var cancelSource = new CancellationTokenSource(1000);
        Task.Run(() =>
        {
            //模拟其他任务
            Thread.Sleep(4000);
            //如果取消请求已发送,则不会显示Task over
            if (!cancelSource.IsCancellationRequested)
                Console.WriteLine("Task over");
        }, cancelSource.Token);
        cancelSource.Token.Register(() => Console.WriteLine("Task cancel"));
    }

    运行后可以看到大约1秒后,控制台显示了Task cancel,并且在没有显示Task over。证明任务被取消了。Task.Run()方法的其中一个重载是接受一个CancelToken参数,我们传入的是cancelSource的Token,这表明cancelSource就可以控制Task的取消与否。我的做法是在1秒后自动取消任务,也可以手动调用cancelSource.Cancel()方法来显式取消任务。我在Token上订阅了取消的事件,在任务被取消后,执行我的方法,这里是控制台打印出Task cancel。也可以在任务中,利用“闭包”,将Token传入到任务中,如下

     1 static void Main(string[] args){
     2     CancelTask();
     3     Console.WriteLine("Async Run");
     4     Console.Read();
     5 }
     6 public static void CancelTask(){
     7     var cancelSource = new CancellationTokenSource(1000);
     8     Task.Run(() =>{
     9         int count = 0;
    10         for (int i = 0; i < 1000; i++)
    11         {
    12             if (cancelSource.IsCancellationRequested)
    13                 break;
    14             count+=i;
    15             Thread.Sleep(30);
    16         }
    17         Console.WriteLine(count);
    18     }, cancelSource.Token);
    19     cancelSource.Token.Register(() => Console.WriteLine("Task cancel"));
    20 }

    手动取消有2个方法,一是cancelSource.Cancel(),另一个是cancelSource.CancelAfter(),示例如下

     1 public static void CancelTask(){
     2     var cancelSource = new CancellationTokenSource();
     3     Task.Run(() =>
     4     {
     5         Thread.Sleep(4000);
     6         Console.WriteLine("Task over");
     7     }, cancelSource.Token);
     8     cancelSource.Token.Register(() => Console.WriteLine("Task cancel"));
     9     //此处调用cancelSource.Cancel()的话会立即结束任务
    10     //该方法会大约1秒后取消任务
    11     cancelSource.CancelAfter(1000);
    12 }

    该示例和上面的在构造函数中传入1000ms的延时是一样的效果,若调用Cancel(),则立刻结束任务。

    以上就是本篇内容,介绍了Task.ContinueWith方法,以及如何取消任务。若文中不能满足你的要求,可以查看诸如Task.Run()或者Task.ContinueWith()方法的几个重载,还有其他的使用方法,本人并没有将全部细枝末节全部学会,主要的目的是掌握多线程的基本使用方法。《CLR via C#》书中也有很详细的讲解,我的这个系列就是从该书中提取,有兴趣的小伙伴可以看一看。欢迎有问题的和我在评论区交流。

    下一篇,我给大家介绍一下C#并行的奇技淫巧:并行的For、Foreach循环以及PLINQ。

  • 相关阅读:
    为什么全局变量一定要初始化?
    SecureCRT 使用密钥登录 Ubuntu
    ubuntu samba 服务器搭建
    Linux 软硬链接的区别及目录权限对软硬链接的影响
    【转载】解析 java 按值传递还是按引用传递
    (转)如何学好C语言
    (转)五个方法成为更好的程序员
    (转)SQLite数据库的加密
    鸟哥Linux私房菜(基础篇)——第十一章:认识与学习Bash
    鸟哥Linux私房菜(基础篇)——第五章:首次登入与在线求助 man page笔记
  • 原文地址:https://www.cnblogs.com/jazzpop/p/8527532.html
Copyright © 2020-2023  润新知