• 异步多线程 Task理解


    一、简介

      Task是.NET Framework4.0 TPL(任务并行库)提供的新的操作线程池线程的封装类。它提供等待、终止(取消)、返回值、完成通知、失败通知、控制执行的先后次序等优化线程操作功能。Task(任务)并不是线程,任务运行的时候需要使用线程,但并不是说任务取代了线程,任务代码是使用底层的线程(Thread或ThreadPool线程)运行的,任务与线程之间并没有一对一的关系。

    二、Task创建与启动

      Task类创建的任务使用的是后台线程,所以在前台线程全部终止的时候,如果任务还没有全部执行完,就会被被动终止。创建一个新的任务时,任务调度器(默认是基于线程池的TaskScheduler调度器,ThreadPoolTaskScheduler)会找到一个最合适的工作者线程,然后将任务加入该工作者线程的本地队列(每个工作者线程都有对应本地队列),任务所包含的代码会在该线程中运行。

      

      先定义一个任务要执行的方法:

      

    static void NewTask()
            {
                Console.WriteLine("开始一个任务");
                Console.WriteLine("Task id:{0}",Task.CurrentId);
                Console.WriteLine("任务执行完成");
            }

      再创建和启动一个任务,有以下三种方式:

      1、使用TaskFactory创建一个任务

         TaskFactory tf = new TaskFactory();

        //任务就会立即启

        Task t1 = tf.StartNew(NewTask);

      2、使用Task类的Factory创建一个任务

        Task t2 = Task.Factory.StartNew(NewTask);

      3、Task构造函数并Start

        Task t3 = new Task(NewTask);

        t3.Start();

        Task t4 = new Task(NewTask, TaskCreationOptions.PreferFairness);

        t4.Start();

        //因为任务是后台线程,所以我们这里阻塞主线程一秒钟来等待任务全部执行完成

        Thread.Sleep(1000);

      注意:使用Task类的构造函数(第3种)和TaskFactory类的StartNew()方法(第2种)时,都可以传递TaskCreationOptions枚举中的值。TaskCreationOptions如下:

      

      注意:如果当前Task上的TaskCreationOptions设置为LongRunning的话,这个task就会委托到Thread中去执行,长时间运行的task占用着ThreadPool的线程,这时候ThreadPool为了保证线程充足,会再次开辟一些Thread,如果耗时任务此时释放了,会导致ThreadPool线程过多,上下文切换频繁,所以此时让Task在Thread中执行还是非常不错的选择,当然不指定这个LongRunning的话,就是在ThreadPool上执行。

    三、Task状态与生命周期 

      任务Task有以下代表任务完成时状态的属性:

        1、IsCanceled,因为被取消而完成

        2、IsCompleted,成功完成

        3、IsFaulted,因为发生异常而完成

      任务并没有提供回调事件来通知完成(像BackgroundWorker一样),通过启用一个新任务的方式来完成类似的功能。 ContinueWith方法可以在一个任务完成的时候发起一个新任务,天然支持了任务的完成通知,可以在新任务中获取原任务的结果值。

    四、Parallel

      使用Parallel.For、Parallel.ForEach的循环迭代的并行执行,TPL会在后台创建System.Threading.Tasks.Task的实例。使用Parallel.Invoke时,TPL也会创建与调用的委托数目一致的System.Threading.Tasks.Task的实例。

    五、Task异常处理 

      当很多任务并行运行的时候,可能会并行发生很多异常。Task实例能够处理一组一组的异常,这些异常有System.AggregateException类处理。

    class Program
        {
            private static ConcurrentQueue<Product> queue = null;
            static void Main(string[] args)
            {
                queue = new ConcurrentQueue<Product>();
                System.Threading.CancellationTokenSource token = new CancellationTokenSource();
                Task tk1 = Task.Factory.StartNew(() => SetProduct(token.Token));
                Thread.Sleep(2000);
           //检查状态,是否因为异常而导致失败
    if (tk1.IsFaulted) { //循环输出异常 foreach (Exception ex in tk1.Exception.InnerExceptions) { Console.WriteLine("tk1 Exception:{0}", ex.Message); } } Console.ReadLine(); } static void SetProduct(System.Threading.CancellationToken ct) { for (int i = 0; i < 5; i++) { throw new Exception(string.Format("Exception Index {0}", i)); } Console.WriteLine("SetProduct 执行完成"); } }

    六、Task取消

      通过取消标记来中断Task实例的执行。 CancellationTokenSource,CancellationToken下的IsCanceled属性标志当前是否已经被取消,取消任务,任务也不一定会马上取消。

    class Program
        {
            private static ConcurrentQueue<Product> queue = null;
            /*  coder:释迦苦僧    */
            static void Main(string[] args)
            {
                queue = new ConcurrentQueue<Product>();
                System.Threading.CancellationTokenSource token = new CancellationTokenSource();
                Task tk1 = Task.Factory.StartNew(() => SetProduct(token.Token));
                Task tk2 = Task.Factory.StartNew(() => SetProduct(token.Token));
                Thread.Sleep(10);
                //取消任务操作
                token.Cancel();
                try
                {
                    //等待完成
                    Task.WaitAll(new Task[] { tk1, tk2 });
                }
                catch (AggregateException ex)
                {
                    //如果当前的任务正在被取消,那么还会抛出一个TaskCanceledException异常,这个异常包含在AggregateException异常中
                    Console.WriteLine("tk1 Canceled:{0}", tk1.IsCanceled);
                    Console.WriteLine("tk1 Canceled:{0}", tk2.IsCanceled);
                }
    
                Thread.Sleep(2000);
                Console.WriteLine("tk1 Canceled:{0}", tk1.IsCanceled);
                Console.WriteLine("tk1 Canceled:{0}", tk2.IsCanceled);
                Console.ReadLine();
            }
            static void SetProduct(System.Threading.CancellationToken ct)
            {
                 //每一次循环迭代,都会有新的代码调用 ThrowIfCancellationRequested 
                 //这行代码能够对 OpreationCanceledException 异常进行观察
                 //并且这个异常的标记与Task实例关联的那个标记进行比较,如果两者相同 ,而且IsCancelled属性为True,那么Task实例就知道存在一个要求取消的请求,并且会将状态转变为Canceled状态,中断任务执行。  
                 //如果当前的任务正在被取消,那么还会抛出一个TaskCanceledException异常,这个异常包含在AggregateException异常中
                //检查取消标记
                ct.ThrowIfCancellationRequested();
                for (int i = 0; i < 50000; i++)
                {
                    Product model = new Product();
                    model.Name = "Name" + i;
                    model.SellPrice = i;
                    model.Category = "Category" + i;
                    queue.Enqueue(model);
                
                    ct.ThrowIfCancellationRequested();
                }
                Console.WriteLine("SetProduct   执行完成");
            }
        }
        class Product
        {
            public string Name { get; set; }
            public string Category { get; set; }
            public int SellPrice { get; set; }
        }

    七、Task.WaitAll 并限定时长

      10毫秒没有完成任务,则输出了****

      queue = new ConcurrentQueue<Product>();
                Task tk1 = new Task(() => { SetProduct(1); SetProduct(3);});
                Task tk2 = new Task(() => SetProduct(2));
                tk1.Start();
                tk2.Start();
    
                //如果tk1 tk2 没能在10毫秒内完成 则输出 *****  
                if (!Task.WaitAll(new Task[] { tk1, tk2 }, 10))
                {
                    Console.WriteLine("******");
                }
              
                Console.ReadLine();

    八、Task返回值  Task<TResult>

     class Program
        {
            
            static void Main(string[] args)
            {
                Task<List<Product>> tk1 = Task<List<Product>>.Factory.StartNew(() => SetProduct());
                Task.WaitAll(tk1);
                Console.WriteLine(tk1.Result.Count);
                Console.WriteLine(tk1.Result[0].Name);
                Console.ReadLine();
            }
            static List<Product> SetProduct()
            {
                List<Product> result = new List<Product>();
                for (int i = 0; i < 500; i++)
                {
                    Product model = new Product();
                    model.Name = "Name" + i;
                    model.SellPrice = i;
                    model.Category = "Category" + i;
                    result.Add(model);
                }
                Console.WriteLine("SetProduct   执行完成");
                return result;
            }
        }

    九、连续执行多个任务 ContinueWith

    class Program
        {
           
            static void Main(string[] args)
            {
                //创建任务t1
                Task t1 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("执行 t1 任务");
                    SpinWait.SpinUntil(() =>
                    {
                        return false;
                    }, 2000);
    
                });
                //创建任务t2   t2任务的执行 依赖与t1任务的执行完成
                Task t2 = t1.ContinueWith((t) =>
                {
                    Console.WriteLine("执行 t2 任务"); 
                    SpinWait.SpinUntil(() =>
                    {
                        return false;
                    }, 2000);
    
                });    
                //创建任务t3   t3任务的执行 依赖与t2任务的执行完成
                Task t3 = t2.ContinueWith((t) =>
                {
                    Console.WriteLine("执行 t3 任务");
                });
                Console.ReadLine();
            }
        }
  • 相关阅读:
    谁在TDD
    开源许可证简单总结
    【转】IIS HTTP500错误以及COM+应用程序8004e00f错误的解决方法
    [原]Linux平台Boost的编译方法
    [原]linux下格式化磁盘的相关问题
    [原]编译MongoDB,C++连接MongoDB测试
    [转]谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词(科普)
    [转]linux下如何查看文件编码格式及转换文件编码
    [原]linux(虚拟机)下安装MySQL
    [转]Linux下比较全面的监控工具dstat
  • 原文地址:https://www.cnblogs.com/shawnhu/p/8427741.html
Copyright © 2020-2023  润新知