在C#4.0之前需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务。线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值。但是在C#4.0中引人了一个的任务(System.Threading.Tasks命名空间的类型)机制来解决异步操作完成时间和完成后返回值的问题。

1.使用Task类创建并执行简单任务

通过使用Task的构造函数来创建任务,并调用Start方法来启动任务并执行异步操作。创建任务时,必须传递一个Action或Action<Object>类型的委托回调方法,可以选择的传递任务执行时说需要的数据对象等。Task类的构造函数如下:

        public Task(Action action);
public Task(Action<object> action, object state);
public Task(Action action, CancellationToken cancellationToken);
public Task(Action action, TaskCreationOptions creationOptions);
public Task(Action<object> action, object state, CancellationToken cancellationToken);
public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);

示例代码:

   1:  static void Main(string[] args)
   2:          {
   3:              Console.WriteLine("主线程执行业务处理.");
   4:              //创建任务
   5:              Task task = new Task(() => {
   6:                  Console.WriteLine("使用System.Threading.Tasks.Task执行异步操作.");
   7:                  for (int i = 0; i < 10; i++)
   8:                  {
   9:                      Console.WriteLine(i);
  10:                  }
  11:              });
  12:              //启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler)
  13:              task.Start();
  14:              Console.WriteLine("主线程执行其他处理");
  15:              //主线程挂起1000毫秒,等待任务的完成。
  16:              Thread.Sleep(1000);
  17:          }

任务调度结果:

2.等待任务的完成并获取返回值

使用任务执行异步操作时,最主要的是要后的任务完成时的返回值。在任务类中有一个实例方法Wait(有许多重载版本)他能等待任务的完成,我们也可以通过Task类的派生类Task<TResult>创建一个异步任务,并指定任务完成时返回值的类型,这样可以通过Task<TResult>的实例对象获取到任务完成后的返回值。创建一个异步任务并执行0到100求和操作返回最后的计算结果,示例代码:

   1:  static void TaskWait() {
   2:              //创建任务
   3:              Task<int> task = new Task<int>(() =>
   4:              {
   5:                  int sum = 0;
   6:                  Console.WriteLine("使用Task执行异步操作.");
   7:                  for (int i = 0; i < 100; i++)
   8:                  {
   9:                      sum+=i;
  10:                  }
  11:                  return sum;
  12:              });
  13:              //启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler)
  14:              task.Start();
  15:   
  16:              Console.WriteLine("主线程执行其他处理");
  17:              //等待任务的完成执行过程。
  18:              task.Wait();
  19:              //获得任务的执行结果
  20:              Console.WriteLine("任务执行结果:{0}", task.Result.ToString());
  21:  }

执行结果:

Task类还有一些静态方法,WaitAll用于等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程和Wait用于等待提供的任一个 System.Threading.Tasks.Task 对象完成执行过程,这两个方法都有一些重载版本。

//等待所有任务完成
public static void WaitAll(params Task[] tasks);
//等待任意一个任务完成
public static int WaitAny(params Task[] tasks);

3.使用ContinueWith方法在任务完成时启动一个新任务

在使用能够Task类的Wait方法等待一个任务时或派生类的Result属性获得任务执行结果都有可能阻塞线程,为了解决这个问题可以使用ContinueWith方法,他能在一个任务完成时自动启动一个新的任务来处理执行结果。

示例代码:

   1:  static void TaskContinueWith()
   2:          {
   3:              //创建一个任务
   4:              Task<int> task = new Task<int>(() =>
   5:              {
   6:                  int sum = 0;
   7:                  Console.WriteLine("使用Task执行异步操作.");
   8:                  for (int i = 0; i < 100; i++)
   9:                  {
  10:                      sum += i;
  11:                  }
  12:                  return sum;
  13:              });
  14:              //启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler)
  15:              task.Start();
  16:              Console.WriteLine("主线程执行其他处理");
  17:              //任务完成时执行处理。
  18:              Task cwt = task.ContinueWith(t => { 
  19:                  Console.WriteLine("任务完成后的执行结果:{0}", t.Result.ToString()); 
  20:              });
  21:              Thread.Sleep(1000);
  22:          }

执行结果:

上述示例中任务不是等待完成来显示执行结果,而是使用ContinueWith方法,它能够知道任务在什么时候完成并启动一个新的任务来执行任务完成后的处理。ContinueWith方法具有一些重载版本,这些重载版本允许指定延续任务需要使用的数据、延续任务的工作方式(System.Threading.Tasks.TaskContinuationOptions的枚举值按位OR运行的结果)等。

4.创建父子任务和任务工厂的使用

通过Task类创建的任务是顶级任务,可以通过使用 TaskCreationOptions.AttachedToParent 标识把这些任务与创建他的任务相关联,所有子任务全部完成以后父任务才会结束操作。示例如下:

   1:  static void ParentChildTask() {
   2:              Task<string[]> parent = new Task<string[]>(state => {
   3:                  Console.WriteLine(state);
   4:                  string[] result=new string[2];
   5:                  //创建并启动子任务
   6:                  new Task(() => { result[0]= "我是子任务1。"; },TaskCreationOptions.AttachedToParent).Start();
   7:                  new Task(() => { result[1] = "我是子任务2。"; }, TaskCreationOptions.AttachedToParent).Start();
   8:                  return result;
   9:              },"我是父任务,并在我的处理过程中创建多个子任务,所有子任务完成以后我才会结束执行。");
  10:              //任务处理完成后执行的操作
  11:              parent.ContinueWith(t => {
  12:                  Array.ForEach(t.Result, r=>Console.WriteLine(r));
  13:              });
  14:              //启动父任务
  15:              parent.Start();
  16:              Console.Read();
  17:          }

执行结果:

如果需要创建一组具有相同状态的任务时,可以使用TaskFactory类或TaskFactory<TResult>类。这两个类创建一组任务时可以指定任务的CancellationToken、TaskCreationOptions、TaskContinuationOptions和TaskScheduler默认值。示例代码:

   1:          static void TaskFactoryApply()
   2:          {
   3:              Task parent = new Task(() =>
   4:              {
   5:                  CancellationTokenSource cts = new CancellationTokenSource(5000);
   6:                  //创建任务工厂
   7:                  TaskFactory tf = new TaskFactory(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
   8:                  //添加一组具有相同状态的子任务
   9:                  Task[] task = new Task[]{
  10:                      tf.StartNew(() => { Console.WriteLine("我是任务工厂里的第一个任务。"); }),
  11:                      tf.StartNew(() => { Console.WriteLine("我是任务工厂里的第二个任务。"); }),
  12:                      tf.StartNew(() => { Console.WriteLine("我是任务工厂里的第三个任务。"); })
  13:                  };
  14:              });
  15:              parent.Start();
  16:              Console.Read();
  17:          }

执行结果:

5.任务内部实现和任务调度

任务内部有一组构成任务状态的属性,标识任务的唯一Id、表示任务的执行状态(TaskStatus)、任务创建时提供的回调函数的引用和传递给回调函数的数据对象AsyncState、对任务创建时的任务调度对象(TaskScheduler)的引用、对父任务的引用以及对执行上下文的引用和ManualResetEventSlim对象的引用。Task类和Task<TResult>类都实现了标准的释放资源的接口,允许在任务完成处理的时候使用Dispose方法释放资源(关闭ManualResetEventSlim对象实例)。可以使用Task类的CurrentId属性获得正在执行的任务的Id,如果没有任务在执行CurrentId返回值为null,CurrentId是一个int?可空类型的属性。任务执行的生命周期通过TaskStatus类型的一个值来表示,TaskStatus所包含的值:

public enum TaskStatus
{
Created = 0,
WaitingForActivation = 1,
WaitingToRun = 2,
Running = 3,
WaitingForChildrenToComplete = 4,
RanToCompletion = 5,
Canceled = 6,
Faulted = 7,
}

我们可以通过Task类的Exception属性获得任务在执行过程中的所有异常

,Exception是一个AggregateException类型的属性。Task类提供了IsCanceled、IsCompleted、IsFaulted属性来获得任务的完成状态。通过ContinueWith、ContinueWhenAll、ContinueWhenAny和FromAsync创建的后续任务都处于WaitingForActivation 状态,这个状态的任务会在父任务完成后自动执行。

在任务内部由TaskScheduler类调度任务的执行,该类是一个抽象类,FCL中从他派生了两个派生类:ThreadPoolTaskScheduler线程池任务调度器和SynchronizationContextTaskScheduler同步上下文任务调度器。所有任务默认都是采用ThreadPoolTaskScheduler调度任务,他是采用线程池来执行任务,可以通过TaskScheduler类的静态属性Default获得对默认任务调度器的引用。SynchronizationContextTaskScheduler任务调度器能够用在Window form、WPF等应用程序,他的任务调度是采用的GUI线程,所以他能同步更新UI组件,可以通过TaskScheduler类的静态方法FromCurrentSynchronizationContext获得对一个同步上下文任务调度起的引用。

任务调度示例:

   1:    private void button1_Click(object sender, EventArgs e)
   2:          {
   3:               //获得同步上下文任务调度器
   4:             TaskScheduler m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
   5:   
   6:              //创建任务,并采用默认任务调度器(线程池任务调度器)执行任务
   7:              Task<int> task = new Task<int>(() =>
   8:              {
   9:                  //执行复杂的计算任务。
  10:                  Thread.Sleep(2000);
  11:                  int sum = 0;
  12:                  for (int i = 0; i < 100; i++)
  13:                  {
  14:                      sum += i;
  15:                  }
  16:                  return sum;
  17:              });
  18:               var cts=new CancellationTokenSource();
  19:              //任务完成时启动一个后续任务,并采用同步上下文任务调度器调度任务更新UI组件。
  20:              task.ContinueWith(t => {this.label1.Text="采用SynchronizationContextTaskScheduler任务调度器更新UI。\r\n计算结果是:"+task.Result.ToString(); },
  21:                 cts.Token ,TaskContinuationOptions.AttachedToParent,m_syncContextTaskScheduler);
  22:              task.Start();
  23:          }

执行结果:

本文简单的介绍了使用Task类来执行异步操作以及任务的内部实现与任务调度。在执行复杂异步操作时,可以采用任务来执行,他能更好的知道异步操作在何时完成以及返回异步操作的执行结果。

C# 线程知识--使用Task执行异步操作的更多相关文章

  1. C# 线程知识--使用ThreadPool执行异步操作

    C# 线程知识--使用ThreadPool执行异步操作 在应用程序中有许多复杂的任务,对于这些任务可能需要使用一个或多个工作线程或I/O线程来协作处理,比如:定时任务.数据库数据操作.web服务.文件 ...

  2. C# 使用Task执行异步操作

    为什么要使用 Task Task 和 Thread 区别 Task 介绍 Task 简单实现 Task 执行状态 为什么要使用 Task 线程是创建并发的底层工具,因此具有一定的局限性. 没有简单的方 ...

  3. C#执行异步操作的几种方式比较和总结

    C#执行异步操作的几种方式比较和总结 0x00 引言 之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理.对XxxxxAsync()之类的方法也没去了解过, ...

  4. [C#] 走进异步编程的世界 - 在 GUI 中执行异步操作

    走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5877042.html 序 这是继<开始接 ...

  5. rxjava源码中的线程知识

    rxjava源码中的线程知识 rx的最精简的总结就是:异步 这里说一下以下的五个类 1.Future2.ConcurrentLinkedQueue3.volatile关键字4.AtomicRefere ...

  6. 【C#】C#线程_I/O限制的异步操作

    目录结构: contents structure [+] 为什么需要异步IO操作 C#的异步函数 async和await的使用 async和Task的区别 异步函数的状态机 异步函数如何转化为状态机 ...

  7. 【C#】C#线程_计算限制的异步操作

    目录结构: contents structure [+] 线程池简介 执行上下文(Execution Context) CancelTokenSource的使用 ThreadPool Task和Tas ...

  8. C#执行异步操作的几种方式比较和总结(转发:https://www.cnblogs.com/durow/p/4826653.html)

    0x00 引言 之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理.对XxxxxAsync()之类的方法也没去了解过,倒也没遇到什么大问题.最近因为需求要求 ...

  9. 走进异步编程的世界 - 在 GUI 中执行异步操作

    转载:https://www.cnblogs.com/liqingwen/p/5877042.html 走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://w ...

随机推荐

  1. [Windows Phone 8]如何解决Lumia手机无法进入系统或开启?

    原文:[Windows Phone 8]如何解决Lumia手机无法进入系统或开启? 摘要 相信有的人一定有遇过手机在更新的时候,齿轮转不停,过了好几小时还是一样,这就代表系统出现问题了,如今Nokia ...

  2. Python在信号与系统(1)——Hilbert兑换,Hilbert在国家统计局的包络检测应用,FIR_LPF滤波器设计,格鲁吉亚也迫使高FM(PM)调制

    谢谢董老师,董老师是个好老师. 心情久久不能平静,主要是高频这门课的分析方法实在是让我难以理解,公式也背只是,还是放放吧. 近期厌恶了Matlab臃肿的体积和频繁的读写对我的Mac的损害,所以学习了一 ...

  3. c#操作appsettiongs

    try { //指定要修改的配置文件的路径 Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWe ...

  4. js阻止冒泡

    js阻止冒泡 (ev || event).cancelBubble = true; 标签切换 <script type="text/javascript"> windo ...

  5. paip.java UrlRewrite 的原理and实现 htaccess正則表達式转换

    paip.java UrlRewrite 的原理and实现 htaccess正則表達式转换 #---KEYWORD #-正則表達式 正則表達式 表示 非指定字符串开头的正则 排除指定文件夹.. 作者 ...

  6. 在JBuilder8中使用ANT

    在JBuilder8中使用ANT                                                            作者:翁驰原    在JBuilder8中,Ap ...

  7. SQL中IN,NOT IN,EXISTS,NOT EXISTS的用法和差别

    SQL中IN,NOT IN,EXISTS,NOT EXISTS的用法和差别: IN:确定给定的值是否与子查询或列表中的值相匹配. IN 关键字使您得以选择与列表中的任意一个值匹配的行. 当要获得居住在 ...

  8. WebxFrameworkFilter 请求响应过程

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvTFRpYW5jaGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA ...

  9. HDU 1864最大报销额(一维背包)

    题目地址:HDU 1864 刚上来看着挺麻烦的..细致看了看原来好简单好简单...仅仅要去掉一些不符合要求的发票,剩下的就是最简单的背包问题了..对于小数问题,仅仅要*100就变成整数了. 代码例如以 ...

  10. 设计模式模式游客(Visitor)摘录

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例,怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化托付给还 ...