4.1 创建Task

Task Parallel Library,三种启动方式
Task(委托).Start()
Task.Run(委托)
Task.Factory.StartNew(委托,TaskCreationOptions.LongRunning)//第二种启动是这种的简略形式

4.2 Task

Task task 可以有返回值,但是在主线程中获取返回值会阻塞
Task.RunSynchronously()方法启动,可以避免使用线程池来执行短暂的操作。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Threading;
  7. using System.IO;
  8. namespace TaskStatusMethod {
  9. class Program {
  10. static void Main(string[] args) {
  11. TaskMethod("Task1");
  12. var task2 = CreateTask("Task2");
  13. task2.Start();
  14. int result = task2.Result;
  15. Console.WriteLine($"Result is:{result}");
  16. task2 = CreateTask("Task3");
  17. task2.RunSynchronously();
  18. result = task2.Result;
  19. Console.WriteLine($"Result is: {result}");
  20. task2 = CreateTask("Task4");
  21. Console.WriteLine(task2.Status);
  22. task2.Start();
  23. while (!task2.IsCompleted) {
  24. Console.WriteLine(task2.Status);
  25. }
  26. Console.WriteLine(task2.Status);
  27. string path = System.Environment.CurrentDirectory;
  28. path = path + "\\" + "test.csv";
  29. if(!File.Exists(path)) {
  30. File.Create(path).Close();
  31. }
  32. //File.Delete(path);
  33. StreamWriter sw = new StreamWriter(path, false, Encoding.UTF8);
  34. sw.Write("2155,adfa,daf,df,g,ag,ag,ag,ag,sfg,s,gsf,sgf");
  35. sw.Flush();
  36. sw.Close();
  37. Console.WriteLine("wancheng");
  38. Console.ReadLine();
  39. }
  40. static Task<int> CreateTask(string name) {
  41. return new Task<int>(() => TaskMethod(name));
  42. }
  43. static int TaskMethod(string name) {
  44. Console.WriteLine($"Task { name} is running on a thread id"+
  45. $"{Thread.CurrentThread.ManagedThreadId} Is thread pool thread:"+
  46. $"{Thread.CurrentThread.IsThreadPoolThread}");
  47. Thread.Sleep(TimeSpan.FromSeconds(2));
  48. return 42;
  49. }
  50. }
  51. }

4.3 组合任务

在本节中,体现了任务其中一个强大的功能,那就是组合任务。通过组合任务可很好的描述任务与任务之间的异步、同步关系,大大降低了编程的难度。
组合任务主要是通过task.ContinueWith()task.WhenAny()task.WhenAll()等和task.GetAwaiter().OnCompleted()方法来实现。
在使用task.ContinueWith()方法时,需要注意它也可传递一系列的枚举选项TaskContinuationOptions,该枚举选项和TaskCreationOptions类似,其具体定义如下表所示。

成员名称 说明
AttachedToParent 如果延续为子任务,则指定将延续附加到任务层次结构中的父级。 只有当延续前面的任务也是子任务时,延续才可以是子任务。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意,如果使用 DenyChildAttach 选项配置父任务,则子任务中的 AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。有关更多信息,请参见Attached and Detached Child Tasks
DenyChildAttach 指定任何使用 TaskCreationOptions.AttachedToParent 选项创建,并尝试作为附加的子任务执行的子任务(即,由此延续创建的任何嵌套内部任务)都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务
ExecuteSynchronously 指定应同步执行延续任务。 指定此选项后,延续任务在导致前面的任务转换为其最终状态的相同线程上运行。如果在创建延续任务时已经完成前面的任务,则延续任务将在创建此延续任务的线程上运行。 如果前面任务的 CancellationTokenSource 已在一个 finally(在 Visual Basic 中为 Finally)块中释放,则使用此选项的延续任务将在该 finally 块中运行。 只应同步执行运行时间非常短的延续任务。由于任务以同步方式执行,因此无需调用诸如 Task.Wait 的方法来确保调用线程等待任务完成。
HideScheduler 指定由延续通过调用方法(如 Task.RunTask.ContinueWith)创建的任务将默认计划程序 (TaskScheduler.Default) 视为当前的计划程序,而不是正在运行该延续的计划程序。
LazyCancellation 在延续取消的情况下,防止延续的完成直到完成先前的任务。
LongRunning 指定延续将是长期运行的、粗粒度的操作。 它会向 TaskScheduler 提示,过度订阅可能是合理的。
None 如果未指定延续选项,应在执行延续任务时使用指定的默认行为。 延续任务在前面的任务完成后以异步方式运行,与前面任务最终的 Task.Status 属性值无关。 如果延续为子任务,则会将其创建为分离的嵌套任务。
NotOnCanceled 指定不应在延续任务前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled,则前面的任务会取消。 此选项对多任务延续无效。
NotOnFaulted 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted,则前面的任务会引发未处理的异常。 此选项对多任务延续无效。
NotOnRanToCompletion 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion,则前面的任务会运行直至完成。 此选项对多任务延续无效。
OnlyOnCanceled 指定只应在延续前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled,则前面的任务会取消。 此选项对多任务延续无效。
OnlyOnFaulted 指定只有在延续任务前面的任务引发了未处理异常的情况下才应安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted,则前面的任务会引发未处理的异常。OnlyOnFaulted 选项可保证前面任务中的 Task.Exception 属性不是 null。 你可以使用该属性来捕获异常,并确定导致任务出错的异常。 如果你不访问 Exception 属性,则不会处理异常。 此外,如果尝试访问已取消或出错的任务的 Result 属性,则会引发一个新异常。此选项对多任务延续无效。
OnlyOnRanToCompletion 指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion,则前面的任务会运行直至完成。 此选项对多任务延续无效。
PreferFairness 提示 TaskScheduler 按任务计划的顺序安排任务,因此较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
RunContinuationsAsynchronously 指定应异步运行延续任务。 此选项优先于 TaskContinuationOptions.ExecuteSynchronously。

演示代码如下所示,使用ContinueWith()OnCompleted()方法组合了任务来运行,搭配不同的TaskCreationOptionsTaskContinuationOptions来实现不同的效果。

  1. static void Main(string[] args)
  2. {
  3. WriteLine($"主线程 线程 Id {CurrentThread.ManagedThreadId}");
  4. // 创建两个任务
  5. var firstTask = new Task<int>(() => TaskMethod("Frist Task",3));
  6. var secondTask = new Task<int>(()=> TaskMethod("Second Task",2));
  7. // 在默认的情况下 ContiueWith会在前面任务运行后再运行
  8. firstTask.ContinueWith(t => WriteLine($"第一次运行答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread}"));
  9. // 启动任务
  10. firstTask.Start();
  11. secondTask.Start();
  12. Sleep(TimeSpan.FromSeconds(4));
  13. // 这里会紧接着 Second Task运行后运行, 但是由于添加了 OnlyOnRanToCompletion 和 ExecuteSynchronously 所以会由运行SecondTask的线程来 运行这个任务
  14. Task continuation = secondTask.ContinueWith(t => WriteLine($"第二次运行的答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}"),TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);
  15. // OnCompleted 是一个事件 当contiuation运行完成后 执行OnCompleted Action事件
  16. continuation.GetAwaiter().OnCompleted(() => WriteLine($"后继任务完成. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程 {CurrentThread.IsThreadPoolThread}"));
  17. Sleep(TimeSpan.FromSeconds(2));
  18. WriteLine();
  19. firstTask = new Task<int>(() =>
  20. {
  21. // 使用了TaskCreationOptions.AttachedToParent 将这个Task和父Task关联, 当这个Task没有结束时 父Task 状态为 WaitingForChildrenToComplete
  22. var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task",5), TaskCreationOptions.AttachedToParent);
  23. innerTask.ContinueWith(t => TaskMethod("Thrid Task", 2), TaskContinuationOptions.AttachedToParent);
  24. return TaskMethod("First Task",2);
  25. });
  26. firstTask.Start();
  27. // 检查firstTask线程状态 根据上面的分析 首先是 Running -> WatingForChildrenToComplete -> RanToCompletion
  28. while (! firstTask.IsCompleted)
  29. {
  30. WriteLine(firstTask.Status);
  31. Sleep(TimeSpan.FromSeconds(0.5));
  32. }
  33. WriteLine(firstTask.Status);
  34. Console.ReadLine();
  35. }
  36. static int TaskMethod(string name, int seconds)
  37. {
  38. WriteLine($"任务 {name} 正在运行,线程池线程 Id {CurrentThread.ManagedThreadId},是否为线程池线程: {CurrentThread.IsThreadPoolThread}");
  39. Sleep(TimeSpan.FromSeconds(seconds));
  40. return 42 * seconds;
  41. }

运行结果如下图所示,与预期结果一致。其中使用了task.Status来打印任务运行的状态,对于task.Status的状态具体含义如下表所示。

成员名称 说明
Canceled 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。 有关详细信息,请参阅任务取消
Created 该任务已初始化,但尚未被计划。
Faulted 由于未处理异常的原因而完成的任务。
RanToCompletion 已成功完成执行的任务。
Running 该任务正在运行,但尚未完成。
WaitingForActivation 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
WaitingForChildrenToComplete 该任务已完成执行,正在隐式等待附加的子任务完成。
WaitingToRun 该任务已被计划执行,但尚未开始执行。

4.4 APM模式转换为任务 Task.Factory.FromAsync()

  • Option1(执行回调函数)的重载,d.BeginInvoke(参数,回调函数,状态值),d.EndInvoke 先执行【执行函数】而后执行回调函数(状态值传入到回调函数中)等该任务完成后,执行延续任务,延续任务的task.Result为执行函数的返回值。在主线程中一直在轮训查task是否完成,直到延续函数运行完轮训结束。
  • Option2(不执行回调函数)的重载,Task.Factory.FromAsync(d.BeginInvoke,d.EndInvoke,参数,状态值)
  • Option3(执行函数有out形参) IAsyncResult ar = e.BeginInvoke(out 参数值,回调函数,状态值) Task.Factory.FromAsync(ar,_=>e.EndInvoke(out 参数值,ar) ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using static System.Threading.Thread; //APM转Task任务 namespace APMTransTask {
    class Program {

      delegate string AsynchronousTask(string threadName);
      delegate string IncompatibleAsychronousTask(out int threadID);
      static void Main(string[] args) {
          int threadID;
          AsynchronousTask d = TestOne;
          IncompatibleAsychronousTask e = TestTwo;
    
          //Task.Factory.FromAsync()方法进行转换
          //重载1(可执行回调函数):d.BeginInvoke(函数参数,回调函数,状态值),d.EndInvoke
          Console.WriteLine("Option1");
          Task<string> task = Task<string>.Factory.FromAsync(d.BeginInvoke("我是线程名称", CallBack, "我是状态信息"), d.EndInvoke);
          task.ContinueWith(t => Console.WriteLine($"回调函数执行完毕,现在运行延续任务,结果:{t.Result}")); //延续任务的委托类型必须和前任务相同,这里不能使用无参数的形式()=>
    
          while (!task.IsCompleted) {
              Console.WriteLine(task.Status);
              Sleep(TimeSpan.FromSeconds(0.5));
          }
          Console.WriteLine(task.Status);
          Console.WriteLine("任务全部执行完毕");
    
          //重载2(不执行回调函数):d.BeginInvoke,d.EndInvoke,参数,状态值
          Console.WriteLine("=======我是重载2=======");
          Task<string> task2 = Task<string>.Factory.FromAsync(d.BeginInvoke, d.EndInvoke, "我是线程名称2","我是状态值");
          task2.ContinueWith(t => Console.WriteLine($"我是函数执行结果:{t.Result}"));
    
          while (!task2.IsCompleted) {
              Console.WriteLine(task2.Status);
              Sleep(TimeSpan.FromSeconds(0.5));
          }
    
          Console.WriteLine(task2.Status);
          Console.WriteLine("任务全部执行完毕");
    
          //当执行函数形参中含有out输出参数,用此重载,IAsyncResult ia= e.BeginInvoke(out 参数,CallBack,状态值)
          //Task.Factory.FromAsync(ia,_=>e.EndInvoke(out 参数,ia));
          Console.WriteLine("Option 3");
          IAsyncResult ar = e.BeginInvoke(out threadID, CallBack, "委托异步调用");
          task = Task<string>.Factory.FromAsync(ar, _ => e.EndInvoke(out threadID, ar));
    
          task.ContinueWith(t => Console.WriteLine($"任务完成,现在运行续接函数!结果:{t.Result},线程Id {threadID}"));
    
          while (!task.IsCompleted) {
              Console.WriteLine(task.Status);
              Sleep(TimeSpan.FromSeconds(0.5));
          }
          Console.WriteLine(task.Status);
    
          Console.ReadLine();
      }
    
      //回调函数
      static void CallBack(IAsyncResult ar) {
          Console.WriteLine("开始执行回调函数...");
          Console.WriteLine($"传递给回调函数的状态:{ar.AsyncState}");
          Console.WriteLine($"是否为线程池线程:{Thread.CurrentThread.IsThreadPoolThread}");
          Console.WriteLine($"线程池工作线程ID:{Thread.CurrentThread.ManagedThreadId}");
      }
    
      //执行函数One
      static string TestOne(string threadName) {
          Console.WriteLine("开始运行....");
          Console.WriteLine($"是否为线程池线程:{Thread.CurrentThread.IsThreadPoolThread}");
          Thread.Sleep(TimeSpan.FromSeconds(2));
    
          Thread.CurrentThread.Name = threadName;
          return $"线程名:{Thread.CurrentThread.Name}";
      }
    
      //执行函数Two
      static string TestTwo(out int threadId) {
          Console.WriteLine("开始运行...");
          Console.WriteLine($"是否为线程池线程:{Thread.CurrentThread.IsThreadPoolThread}");
          Thread.Sleep(TimeSpan.FromSeconds(2));
    
          threadId = CurrentThread.ManagedThreadId;
          return $"线程池线程工作Id是:{threadId}";
      }
    

    } }

![image.png](https://cdn.nlark.com/yuque/0/2020/png/957395/1606965206507-e5b9bd28-29a2-449a-bdc5-45592abf9af7.png#align=left&display=inline&height=175&margin=%5Bobject%20Object%5D&name=image.png&originHeight=203&originWidth=499&size=12229&status=done&style=none&width=431)
<a name="dQuFK"></a>
# 4.5 将EAP模式转换为任务TaskCompletionSource
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Threading.Thread;
using System.ComponentModel;

//使用TaskCompletionSource类可将EAP模式转换为任务
namespace EAPTranTask {
    class Program {
        static TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
        static void Main(string[] args) {


            var worker = new BackgroundWorker();
            worker.DoWork += Worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            worker.RunWorkerAsync();
            Console.WriteLine($"结果是{tsc.Task.Result}");
            Console.ReadLine();
        }

        private static void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            if (e.Error != null) {
                tsc.SetException(e.Error);
            }
            else if(e.Cancelled){
                tsc.SetCanceled();
            }
            else {
                tsc.SetResult((int)e.Result);
            }
        }

        private static void Worker_DoWork(object sender, DoWorkEventArgs e) {
            e.Result = TaskMethod("后台工作", 5);
        }

        static int TaskMethod(string name, int seconds) {
            Console.WriteLine($"任务{name}运行在线程{CurrentThread.ManagedThreadId}上. 是否为线程池线程{CurrentThread.IsThreadPoolThread}");

            Sleep(TimeSpan.FromSeconds(seconds));

            return 42 * seconds;
        }
    }    
}

4.6 实现取消选择

Task构造函数可以传入一个CancellationToken,通常创建一个CancellationTokenSource cts对象,记住,如果执行cts.Cancel()了,后续还想继续执行任务,需要再new 一次。下面示例是Winform窗体下进行任务开始和取消的简单例子。先通过一个Button1,点击后启动一个Task(将CancellationToken传入其中),Task.Start()则执行,在执行任务中建立一个查询Token.IsCancellationRequested(三种方式,这是第一种,另外两种是:抛出异常token.ThrowIfCancellationRequested,第三种为在取消后执行回调函数token.Register(执行函数委托))。Button2 用于取消,在cts.Cancel()后再new一个新对象以用于下次启动Task传入其中。

namespace CancellationTokenSourceInTask {
    public partial class Form1 : Form {
        private static Task<int> task;
        private static CancellationTokenSource cts = new CancellationTokenSource();

        public Form1() {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e) {
            task = new Task<int>(() => TaskMethod("独立任务1", 2, cts.Token), cts.Token);
            task.Start();
        }

        private void button2_Click(object sender, EventArgs e) {
            Console.WriteLine("准备取消任务");
            cts.Cancel();
            cts = new CancellationTokenSource();
            Console.WriteLine($"独立任务1的运行结果为:{task.Result}");
            Console.ReadLine();
        }

        private static int TaskMethod(string name,int seconds,CancellationToken token) {
            Console.WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:" +
                $"{CurrentThread.IsThreadPoolThread}");
            while (!token.IsCancellationRequested) {
                Console.WriteLine("任务运行中...");
            }
            return 42 * seconds;
        }
    }
}

4.7 trycatch

允许异常继续执行:勾选上
image.png
两种方式:第一种通过task.Result获取异常,但是不会有异常的具体消息
第二种task.GetAwaiter().GetResult()可以捕捉到具体的消息

var complexTask = Task.WhenAll(任务….)组合任务
complexTask.ContinueWith(t=>{})等待两个任务执行完毕再执行此委托指向的方法。
如果要捕捉组合任务的异常,在Exception.InnerExceptions遍历。

namespace TryCatchTask {
    class Program {    
        static void Main(string[] args) {
            Task<int> task;
            Console.WriteLine("====第一种方式====");
            try {
                task = Task<int>.Run(() => TaskMethod("Pattern1", 2, "Boom1"));
                var result1 = task.Result;
                Console.WriteLine($"异常结果为:{task.Result}");
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message);//发生一个或多个错误
            }

            Console.WriteLine("====第二种方式====");
            try {
                task = Task<int>.Run(() => TaskMethod("Pattern1", 2, "Boom1"));
                int result = task.GetAwaiter().GetResult();
                Console.WriteLine($"异常结果为:{result}");
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message);//Boom1
            }

            Console.WriteLine("====组合任务异常====");
            var t1 = new Task<int>(() => TaskMethod("Pattern1", 2, "Boom1"));
            var t2 = new Task<int>(() => TaskMethod("Pattern1", 2, "Boom2"));
            var complexTask = Task.WhenAll(t1, t2);//组合任务

            var exceptionHandler = complexTask.ContinueWith(t => {
                Console.WriteLine(t.Exception.Message);
                foreach (var item in t.Exception.InnerExceptions) {
                    Console.WriteLine($"=========={item.Message}");//Boom1 Boom2
                }
            }, TaskContinuationOptions.OnlyOnFaulted);//只在延续任务之前有异常才执行

            t1.Start();
            t2.Start();
            Console.ReadLine();
        }

        static int TaskMethod(string name, int seconds,string ex) {
            Console.WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}");

            Sleep(TimeSpan.FromSeconds(seconds));
            // 人为抛出一个异常
            throw new Exception(ex);         
            return 42 * seconds;
        }
    }
}

4.8 并行运行任务Task.WhenAll Task.WhenAny

static void Main(string[] args)
{
    // 第一种方式 通过Task.WhenAll 等待所有任务运行完成
    var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
    var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));

    // 当firstTask 和 secondTask 运行完成后 才执行 whenAllTask的ContinueWith
    var whenAllTask = Task.WhenAll(firstTask, secondTask);
    whenAllTask.ContinueWith(t => WriteLine($"第一个任务答案为{t.Result[0]},第二个任务答案为{t.Result[1]}"), TaskContinuationOptions.OnlyOnRanToCompletion);

    firstTask.Start();
    secondTask.Start();

    Sleep(TimeSpan.FromSeconds(4));

    // 使用WhenAny方法  只要列表中有一个任务完成 那么该方法就会取出那个完成的任务
    var tasks = new List<Task<int>>();
    for (int i = 0; i < 4; i++)
    {
        int counter = 1;
        var task = new Task<int>(() => TaskMethod($"Task {counter}",counter));
        tasks.Add(task);
        task.Start();
    }

    while (tasks.Count > 0)
    {
        var completedTask = Task.WhenAny(tasks).Result;
        tasks.Remove(completedTask);
        WriteLine($"一个任务已经完成,结果为 {completedTask.Result}");
    }

    ReadLine();
}

static int TaskMethod(string name, int seconds)
{
    WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(seconds));
    return 42 * seconds;
}

4.9 TaskScheduler配置任务的执行(重要)

Task中,负责任务调度是TaskScheduler对象,FCL提供了两个派生自TaskScheduler的类型:线程池任务调度器(Thread Pool Task Scheduler)同步上下文任务调度器(Synchronization Scheduler)。默认情况下所有应用程序都使用线程池任务调度器,但是在UI组件中,不使用线程池中的线程,避免跨线程更新UI,需要使用同步上下文任务调度器。可以通过执行TaskSchedulerFromCurrentSynchronizationContext()静态方法来获得对同步上下文任务调度器的引用。

第三个实例很重要,将耗时运算放在Task中运行,运行完成后,使用ContineWith执行后续操作,配置任务为同步上下文任务调度器来更新UI 界面

namespace TaskSchedulerExample {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        Task<string> TaskMethod() {
            return TaskMethod(TaskScheduler.Default);//调用线程池任务调度器(Thread Pool Task Scheduler)
            //同步上下文任务调度器 Synchronization Scheduler
        }

        Task<string> TaskMethod(TaskScheduler scheduler) {
            Task delay = Task.Delay(TimeSpan.FromSeconds(10));//当前任务延时10S,相当于sleep,阻塞了主线程
            return delay.ContinueWith(t => { //在当前任务执行后进行延续任务,调用线程池任务调度器执行后续任务,
                string str = $"任务运行在{CurrentThread.ManagedThreadId}上,是否为线程池线程:{CurrentThread.IsThreadPoolThread}";
                Console.WriteLine(str);
                this.textBox_Content.Text = str;//跨线程操作无效
                return str;
            }, scheduler);
        }

        //同步执行 会阻塞主线程
        private void button_Sync_Click(object sender, EventArgs e) {
            Console.WriteLine($"========={CurrentThread.ManagedThreadId}");
            this.textBox_Content.Text = string.Empty;
            try {
                string result = TaskMethod().Result;
                this.textBox_Content.Text = result;
            }
            catch (Exception ex) {

                this.textBox_Content.Text = ex.InnerException.Message;
            }
        }

        //异步执行,不会阻塞主线程,但线程间操作无效
        private void button_Async_Click(object sender, EventArgs e) {
            Console.WriteLine($"========={CurrentThread.ManagedThreadId}");
            this.textBox_Content.Text = string.Empty;
            Cursor.Current = Cursors.WaitCursor;
            try {
                Task<string> task = TaskMethod();
                task.ContinueWith(t => {  //在这个线程中调用改变UI界面
                    this.textBox_Content.Text = t.Exception.InnerException.Message;
                    Cursor.Current = Cursors.Default;
                },CancellationToken.None,TaskContinuationOptions.OnlyOnFaulted,TaskScheduler.FromCurrentSynchronizationContext());

            }
            catch (Exception ex) {

                //this.textBox_Content.Text = ex.InnerException.Message;
            }
        }

        //异步执行且可以更新主线程
        private void button_AsyncOK_Click(object sender, EventArgs e) {
            Task<int> task = new Task<int>(()=> {
                int sum = 0;
                for (int i = 0; i < 100000; i++) {
                    sum += i;
                }
                Thread.Sleep(TimeSpan.FromSeconds(6));
                return sum;
            });
            task.ContinueWith(t => {
                this.textBox_Content.Text = t.Result.ToString();
            }, CancellationToken.None,
                TaskContinuationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
            task.Start();
        }
    }
}