《CLR Via C# 第3版》笔记之(十九) – 任务(Task)

3.2 子任务

定义子任务时,注意早晚要加上TaskCreationOptions.AttachedToParent,这样父任务会等待子任务执行完后才停止。

using System;
using System.Threading.Tasks;
using System.Threading;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");

        Task<int[]> parentTask = new Task<int[]>(() =>
        {
            var result = new int[3];

            // 子任务1
            new Task(() => { 
                Console.WriteLine("sub task 1 start!"); 
                Thread.Sleep(1000);
                Console.WriteLine("sub task 1 end!");
                result[0] = 1;
            }, TaskCreationOptions.AttachedToParent).Start();

            // 子任务2
            new Task(() =>
            {
                Console.WriteLine("sub task 2 start!");
                Thread.Sleep(1000);
                Console.WriteLine("sub task 2 end!");
                result[1] = 2;
            }, TaskCreationOptions.AttachedToParent).Start();

            // 子任务3
            new Task(() =>
            {
                Console.WriteLine("sub task 3 start!");
                Thread.Sleep(1000);
                Console.WriteLine("sub task 3 end!");
                result[2] = 3;
            }, TaskCreationOptions.AttachedToParent).Start();

            return result;
        });

        parentTask.Start();

        Console.WriteLine("Parent Task's Result is :");
        foreach (int result in parentTask.Result)
            Console.Write("{0}\t", result);

        Console.WriteLine();
        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

地点的事例中,可以把TaskCreationOptions.AttachedToParent删掉试试,打印出来的Result应该是3个0,而不是
2   3

3个子任务的施行各种也和概念的依次无关,比如任务3恐怕首先执行(与CPU的调度有关)。

 

2.3 取消Task

撤销Task和注销一个线程类似,使用CancellationTokenSource

using System;
using System.Threading.Tasks;
using System.Threading;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");
        CancellationTokenSource cts = new CancellationTokenSource();

        // 创建2个Task
        Task t1 = new Task(() => { 
            Console.WriteLine("Task1 start");
            for (int i = 0; i < 100; i++)
            {
                if (!cts.Token.IsCancellationRequested)
                {
                    Console.WriteLine("Count : " + i.ToString());
                    Thread.Sleep(1000);
                }
                else
                {
                    Console.WriteLine("Task1 is Cancelled!");
                    break;
                }
            }
            Console.WriteLine("Task1 end");
        }, cts.Token);

        // 启动Task
        t1.Start();
        Thread.Sleep(3000);
        // 运行3秒后取消Task
        cts.Cancel();

        // 为了测试取消操作,主线程等待Task完成
        Task.WaitAny(new Task[] { t1 });
        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

 

2.1 创制并启动一个Task

using System;
using System.Threading.Tasks;
using System.Threading;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");

        // 创建一个Task
        Task t1 = new Task(() => { 
            Console.WriteLine("Task start"); 
            Thread.Sleep(1000);
            Console.WriteLine("Task end");
        });

        // 启动Task
        t1.Start();

        // 主线程并没有等待Task,在Task完成前就已经完成了
        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

 

  • 职责的牵线
  • 任务的大旨使用
  • 子任务和天职工厂
  • 职责调度器
  • 并行任务Parallel

重在内容:

3. 子任务和任务工厂

3.1 延续任务

为了保险程序的紧缩性,应该尽量避免线程阻塞,这就代表大家在等候一个任务完成时,最好不用用Wait,而是让一个任务完毕后活动启动它的下一个职责。

using System;
using System.Threading.Tasks;
using System.Threading;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");

        // 第一个Task
        Task<int> t1 = new Task<int>(() =>
        {
            Console.WriteLine("Task 1 start!");
            Thread.Sleep(2000);
            Console.WriteLine("Task 1 end!");
            return 1;
        });

        // 启动第一个Task
        t1.Start();
        // 因为TaskContinuationOptions.OnlyOnRanToCompletion,
        // 所以第一个Task正常结束时,启动第二个Task。
        // TaskContinuationOptions.OnlyOnFaulted,则第一个Task出现异常时,启动第二个Task
        // 其他可详细参考TaskContinuationOptions定义的各个标志
        t1.ContinueWith(AnotherTask, TaskContinuationOptions.OnlyOnRanToCompletion);

        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }

    // 第二个Task的处理都在AnotherTask函数中,
    // 第二个Task的引用其实就是上面ContinueWith函数的返回值。
    // 这里没有保存第二个Task的引用
    private static void AnotherTask(Task<int> task)
    {
        Console.WriteLine("Task 2 start!");
        Thread.Sleep(1000);
        Console.WriteLine("Task 1's return Value is : " + task.Result);
        Console.WriteLine("Task 2 end!");
    }
}

 

2. 职责的着力使用

C#,下边演示任务的创造,撤废,等待等基本选择办法。

3.3 任务工厂

除了上边的办法,还足以采纳任务工厂来批量开立任务。

using System;
using System.Threading.Tasks;
using System.Threading;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");

        Task<int[]> parentTask = new Task<int[]>(() =>
        {
            var result = new int[3];
            TaskFactory tf = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);

            // 子任务1
            tf.StartNew(() =>
            {
                Console.WriteLine("sub task 1 start!");
                Thread.Sleep(1000);
                Console.WriteLine("sub task 1 end!");
                result[0] = 1;
            });

            // 子任务2
            tf.StartNew(() =>
            {
                Console.WriteLine("sub task 2 start!");
                Thread.Sleep(1000);
                Console.WriteLine("sub task 2 end!");
                result[1] = 2;
            });

            // 子任务3
            tf.StartNew(() =>
            {
                Console.WriteLine("sub task 3 start!");
                Thread.Sleep(1000);
                Console.WriteLine("sub task 3 end!");
                result[2] = 3;
            });

            return result;
        });

        parentTask.Start();

        Console.WriteLine("Parent Task's Result is :");
        foreach (int result in parentTask.Result)
            Console.Write("{0}\t", result);

        Console.WriteLine();
        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

应用任务工厂与地点3.2中直接定义子任务相比较,优势重要在于可以共享子任务的装置,比如在TaskFactory中设置了TaskCreationOptions.AttachedToParent,那么它启动的子任务都独具那些特性了。

自然,任务工厂(TaskFactory)还提供了诸多控制子任务的函数,用的时候可以看看它的类定义。

 

2.2 主线程等待子线程完成

using System;
using System.Threading.Tasks;
using System.Threading;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");

        // 创建2个Task
        Task t1 = new Task(() => { 
            Console.WriteLine("Task1 start"); 
            Thread.Sleep(1000);
            Console.WriteLine("Task1 end");
        });
        Task t2 = new Task(() =>
        {
            Console.WriteLine("Task2 start");
            Thread.Sleep(2000);
            Console.WriteLine("Task2 end");
        });

        // 启动Task
        t1.Start();
        t2.Start();

        // 当t1和t2中任何一个完成后,主线程继续后面的操作
        // Task.WaitAny(new Task[] { t1, t2 });

        // 当t1和t2中全部完成后,主线程继续后面的操作
        Task.WaitAll(new Task[] { t1, t2 });

        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

等待的艺术WaitAllWaitAny可按照使用场景选拔一个。

 

除了上篇中提到的线程池,本篇介绍一种新的实现异步操作的办法–任务(Task)。

 

4. 职责调度器

地点例子中任务的各个操作(运行,等待,裁撤等等),都是由CLR的天职调度器来调度的。

 

FCL公开了2种任务调度器:线程池任务调度器一路上下文任务调度器

默认情形下,应用程序都是行使的线程池任务调度器。WPF和Winform中一般接纳同台上下文任务调度器

 

CLR的任务调度器类(TaskScheduler)中有个Default特性重回的就是线程池任务调度器

还有个FromCurrentSynchronizationContext方法,再次回到的是一路上下文任务调度器

 

咱俩也可以通过继承CLR中的任务调度器(TaskScheduler)来定制符合自己工作需要的职责调度器。

下边我们定制一个概括的TaskScheduler,将3.3中各样子任务的打印新闻的效应移到自定义的任务调度器MyTaskScheduler中。

using System;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");

        Task<int[]> parentTask = new Task<int[]>(() =>
        {
            var result = new int[3];
            // 这里的TaskFactory中指定的是自定义的任务调度器MyTaskScheduler
            TaskFactory tf = new TaskFactory(CancellationToken.None, TaskCreationOptions.AttachedToParent,
                TaskContinuationOptions.None, new MyTaskScheduler());

            // 子任务1
            tf.StartNew(() =>
            {
                Thread.Sleep(1000);
                result[0] = 1;
            });

            // 子任务2
            tf.StartNew(() =>
            {
                Thread.Sleep(1000);
                result[1] = 2;
            });

            // 子任务3
            tf.StartNew(() =>
            {
                Thread.Sleep(1000);
                result[2] = 3;
            });

            return result;
        });

        parentTask.Start();

        Console.WriteLine("Parent Task's Result is :");
        foreach (int result in parentTask.Result)
            Console.Write("{0}\t", result);

        Console.WriteLine();
        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

// 自定义的TaskScheduler,没什么实际的作用,只是为了实验自定义TaskScheduler
public class MyTaskScheduler : TaskScheduler
{
    private IList<Task> _lstTasks;

    public MyTaskScheduler()
    {
        _lstTasks = new List<Task>();
    }

    #region inherit from TaskScheduler
    protected override System.Collections.Generic.IEnumerable<Task> GetScheduledTasks()
    {
        return _lstTasks;
    }

    protected override void QueueTask(Task task)
    {
        _lstTasks.Add(task);
        // 将原先的打印信息,移到此处统一处理
        Console.WriteLine("task " + task.Id + " is start!");
        TryExecuteTask(task);
        Console.WriteLine("task " + task.Id + " is end!");
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return TryExecuteTask(task);
    }
    #endregion
}

 

5. 并行任务Parallel

Parallel是为着简化任务编程而新增的静态类,利用Parallel能够将平常的循环操作都互相起来。

下例演示了for并行循环,foreach并行循环与之类似。

using System;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;

public class CLRviaCSharp_19
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread start!");
        int max = 10;

        // 普通循环
        long start = Stopwatch.GetTimestamp();
        for (int i = 0; i < max; i++)
        {
            Thread.Sleep(1000);
        }
        Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - start);

        // 并行的循环
        start = Stopwatch.GetTimestamp();
        Parallel.For(0, max, i => { Thread.Sleep(1000); });
        Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - start);

        Console.WriteLine("Main Thread end!");
        Console.ReadKey(true);
    }
}

在地点的事例中,采纳互动循环消耗的年月不到原来的一半。

不过,采纳互动循环需要满意一个准绳,就是for循环中的内容可以互为才行

诸如for循环中是个对 循环变量i 举行的充分操作(例如sum +=
i;),这就不可能采取并行循环。

 

还有某些亟需注意,Parallel的艺术本身有付出

就此倘若for循环内的拍卖相比简单的话,那么直接用for循环可能更快一些。

譬如将上例中的Thread.Sleep(1000);删掉,再运行程序意识,直接for循环要快很多。

1. 职责的介绍

接纳ThreadPool的QueueUserWorkItem方法创立的异步操作存在一些范围:

  1. 异步操作没有再次回到值
  2. 没有内建的机制来公告异步操作几时做到

 

而采用任务(Task)来建革新步操作可以制服上述范围,同时还解决了此外部分题目。

任务(Task)对象和线程池相比,多了过多情状字段和章程,便于更好的操纵任务(Task)的运行。

当然,任务(Task)提供大量的意义也是有代价的,意味着更多的内存消耗。所以在实际上利用中,假如不用任务(Task)的附加成效,那么就动用ThreadPool的QueueUserWorkItem方法。

 

通过任务的状态(TaskStatus),可以精通任务(Task)的生命周期。

TaskStatus是一个枚举类型,定义如下:

public enum TaskStatus
{   
    // 运行前状态
    Created = 0,                      // 任务被显式创建,通过Start()开始这个任务
    WaitingForActivation = 1,         // 任务被隐式创建,会自动开始
    WaitingToRun = 2,                 // 任务已经被调度,但是还没有运行

    // 运行中状态
    Running = 3,                      // 任务正在运行
    WaitingForChildrenToComplete = 4, // 等待子任务完成

    // 运行完成后状态
    RanToCompletion = 5,              // 任务正常完成
    Canceled = 6,                     // 任务被取消
    Faulted = 7,                      // 任务出错
}

结构一个Task后,它的气象为Create

起首后,状态变为WaitingToRun

实际在一个线程上运行时,状态变成Running

运作成功后,按照实际情形,状态成为RanToCompletiionCanceledFaulted二种中的一种。

尽管Task不是经过new来创立的,而是经过以下某个函数创立的,那么它的情景就是WaitingForActivation

ContinueWithContinueWhenAllContinueWhenAnyFromAsync。

一经Task是透过结构一个TaskCompletionSource<TResult>对象来创设的,该Task在开创时也是居于WaitingForActivation状态。

 

相关文章