C# 异步编程

     基于Task的异步编程模式(TAP)是Microsoft为.Net平台下使用Task进行编程所提供的一组建议,这种模式提供了能够被await消耗(调用)方法的APIs,而且当使用async关键字编写遵照这种模式的方法时,手写Task一般颇有用。一般TAP用起来与普通方式没什么两样,可是不支持ref和out参数。html

     任务和线程的区别:
     一、任务是架构在线程之上的,也就是说任务最终仍是要抛给线程去执行。android

     二、任务跟线程不是一对一的关系,好比开10个任务并非说会开10个线程,这一点任务有点相似线程池,可是任务相比线程池有很小的开销和精确的控制。ios

     三、Task的优点git

  ThreadPool相比Thread来讲具有了不少优点,可是ThreadPool却又存在一些使用上的不方便。好比:
  ◆ ThreadPool不支持线程的取消、完成、失败通知等交互性操做;
  ◆ ThreadPool不支持线程执行的前后次序;
     以往,若是开发者要实现上述功能,须要完成不少额外的工做,如今,微软提供了一个功能更强大的概念:Task。Task在线程池的基础上进行了优化,并提供了更多的API。github

 下面分析一理异步编程中的一些关键点编程

1.await  

     咱们都知道await关键字是.Net FrameWork4.5引入的特性。await使得咱们使用异步更加时特别便捷,而且还不会致使线程堵塞。咱们在使用时也就莫名其妙的使用。每每不知道为何不会致使线程堵塞。在这里,简单的谈论下await的一点原理。c#

     在c#并行编程这本书中是这么介绍await的:async方法在开始时以同步方式执行,在async方法内部,await关键字对它参数执行一个异步等待,它首先检查操做是否已经完成,若是完成,就继续运行(同步方式),不然,会暂停async方法,并返回.留下一个未完成的task,一段时间后,操做完成,async方法就恢复执行.api

     看到这句话应该就差很少能想到await为何不会致使线程堵塞了,当碰到await时若是没有执行成功就先暂停这个方法的执行,执行方法外如下代码,等await操做完成后再执行这个方法await以后的代码。架构

1 private  void button1_Click(object sender, EventArgs e)
2 {
3       DemoAsync();
4       MessageBox.Show("同步代码");
5 }
View Code

     运行后会发如今点击button按钮时窗体不能被移动了,而后等待了3秒钟才弹出"同步代码"这句话。异步

1 async Task DemoAsync()
2 {
3       await Task.Run(() => { Thread.Sleep(3000); });
4       Thread.Sleep(3000);
5 }
View Code

     再次运行就会神奇的发现,此时会先弹出"同步代码"这局话,而后等待3秒后窗体就不能被移动。

2.Task

     Task 类表示一般以异步方式执行的单个操做, Task 对象是基于任务的异步模式的中心组件之一。 因为 Task 对象执行的工做一般在线程池线程上异步执行,而不是在主应用程序线程上同步执行,所以可使用 Status 属性,还可使用 IsCanceledIsCompleted和 IsFaulted 属性,用于肯定任务的状态。 一般,lambda 表达式用于指定任务要执行的工做。能够经过多种方式建立 Task 实例。 最多见的方法(从 .NET Framework 4.5开始提供)是调用静态 Run 方法。 Run 方法提供一种简单的方法来使用默认值启动任务,而无需其余参数。Task 类还提供了初始化任务的构造函数,但不计划执行该任务。 出于性能缘由,Task.Run 或 TaskFactory.StartNew 方法是用于建立和计划计算任务的首选机制,但对于必须分隔建立和计划的状况,可使用构造函数,而后调用 Task.Start用于计划任务稍后执行的方法。由于任务一般在线程池线程上以异步方式运行,因此,建立和启动任务的线程会在实例化任务后当即继续执行。 在某些状况下,当调用线程是主应用程序线程时,应用程序可能会在任何任务实际开始执行以前终止。 在其余状况下,应用程序的逻辑可能要求调用线程在一个或多个任务完成执行时继续执行。 能够经过调用 Wait 方法来等待一个或多个任务完成,从而同步调用线程的执行以及它启动的异步任务。若要等待单个任务完成,能够调用其 Task.Wait 方法。 Wait(Int32) 和 Wait(TimeSpan) 方法会阻止调用线程,直到任务完成或超时间隔结束。

     须要注意的是,Task中的方法通常是不会将异常抛出的,哪怕是Task<T>这种,这须要开发人员自行处理。不过若是获取Task<T>.Result则会将任务内的异常抛出来;

 1 public static async Task<string> Hello(int a=0)
 2 {
 3     return await Task.Run(async () =>
 4     {
 5         await Task.Delay(1000).ConfigureAwait(true);
 6 
 7         return $"Hello {10/a}";
 8 
 9     }).ConfigureAwait(true);
10 }
11 //不会报异常
12 Hello(0);
13 //会报异常
14 var a1=  Hello(0).Result;
View Code

Task.Factory提供了更多参数的Task建立方式以支持自定义的Task,好比:Task,Factory.StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)就支持指定取消通知参数、任务建立模式,指定任务调度器;其中TaskCreationOptions参数意义以下:

AttachedToParent 4

指定将任务附加到任务层次结构中的某个父级。 默认状况下,子任务(即由外部任务建立的内部任务)将独立于其父任务执行。 可使用 AttachedToParent 选项以便将父任务和子任务同步。

请注意,若是使用 DenyChildAttach 选项配置父任务,则子任务中的 AttachedToParent 选项不起做用,而且子任务将做为分离的子任务执行。

有关详细信息,请参阅附加和分离的子任务

DenyChildAttach 8

指定任未尝试做为附加的子任务执行(即,使用 AttachedToParent 选项建立)的子任务都没法附加到父任务,会改为做为分离的子任务执行。 有关详细信息,请参阅 附加和分离的子任务

HideScheduler 16

防止环境计划程序被视为已建立任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 建立任务的执行操做将被视为 Default 当前计划程序。

LongRunning 2

指定任务将是长时间运行的、粗粒度的操做,涉及比细化的系统更少、更大的组件。 它会向 TaskScheduler 提示,过分订阅多是合理的。 能够经过过分订阅建立比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务须要附加线程,以使任务不阻塞本地线程池队列中其余线程或工做项的向前推进。

None 0

指定应使用默认行为。

PreferFairness 1

提示 TaskScheduler 以一种尽量公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。

RunContinuationsAsynchronously 64

强制异步执行添加到当前任务的延续任务。

请注意,RunContinuationsAsynchronously 成员在以 .NET Framework 4.6 开头的 TaskCreationOptions 枚举中可用。

3.Task.ConfigureAwait

     在Task里中有ConfigureAwait这么一个方法,这个方法是干什么的呢,咱们先看下方法注释是怎么解释这个方法的:" 尝试将延续任务封送回原始上下文,则为 true;不然为 false。"  光看这段代码并看不出什么,而后咱们再看这么一段话:"一个async方法是由多个同步执行的程序块组成.每一个同步程序块之间由await语句分隔.用await语句等待一个任务完成.当该方法在await处暂停时,就能够捕捉上下文(context).若是当前SynchronizationContext不为空,这个上下文就是当前SynchronizationContext.若是为空,则这个上下文为当前TaskScheduler.该方法会在这个上下文中继续运行.通常来讲,运行UI线程时采用UI上下文,处理ASP.NET请求时采用ASP.NET请求上下文,其它不少状况则采用线程池上下文。" 这句话已经基本讲明了其实后续代码会下上文中执行。这个上下文通常时UI上下文(运行在UI上)或请求上下文(ASP.NET) 这两个能够说时原始上下文,而其它状况采用线程池上下文,也就是开辟一个新线程。这么说也就是ConfigureAwait方法是将后续代码是送到原始上下文仍是线程池上下文中。

     下面稍微修改下刚才的方法:

1 async Task DemoAsync()
2 {
3      //将后续代码交给线程池执行
4      await Task.Run(() => { Thread.Sleep(3000); }).ConfigureAwait(false);
5      Thread.Sleep(3000);
6 }
View Code

     ConfigureAwait(false)将后续代码交给线程池来执行,也就是上面的Thread.Sleep并不会阻塞窗体。

4.Task.Delay

     在异步编程中,通常不建议使用Thread.Sleep,而是使用粒度更小的Task.Delay;Thread.Sleep、Thread.Yeild等会让当前工做线程阻塞,而Task.Delay可让当前线程空出来去完成其余的Task。

 1       CancellationTokenSource source = new CancellationTokenSource();
 2 
 3       var t = Task.Run(async delegate
 4               {
 5                  await Task.Delay(TimeSpan.FromSeconds(1.5), source.Token);
 6                  return 42;
 7               });
 8       source.Cancel();
 9       try {
10          t.Wait();
11       }
12       catch (AggregateException ae) {
13          foreach (var e in ae.InnerExceptions)
14             Console.WriteLine("{0}: {1}", e.GetType().Name, e.Message);
15       }
View Code

5.SemaphoreSlim

     表示对可同时访问资源或资源池的线程数加以限制的 Semaphore 的轻量替代, SemaphoreSlim类是用于在单个应用内进行同步的建议信号量。在异步编程过程当中,建议使用SemaphoreSlim.Wait()、SemaphoreSlim.Release()来替换Lock,由于lock只能由当前线程来解锁,而SemaphoreSlim能够由任意线程来解锁。

6.CancellationTokenSource 

     启动一个Task去作一些事,若是但愿它在某个阶段去被动的中止,可使用这个CancellationTokenSource对象,把它注入到Task里,使用当外界触发Cancel()方法或设置了超时时,这个Task就会被取消。一般和CancellationToken.IsCancellationRequested一块儿配合Task来使用。

 1         public static async void Loop(int timeOut = 5 * 1000)
 2         {
 3             using (CancellationTokenSource source = new CancellationTokenSource(timeOut))
 4             {
 5                 CancellationToken token = source.Token;
 6 
 7                 //匿名异步方法
 8                 var task1 = Task.Run(async () =>
 9                 {
10                     while (!token.IsCancellationRequested)
11                     {
12                         Console.WriteLine($"{DateTime.Now.ToLongTimeString()}\t TaskTest Loop");
13                         await Task.Delay(1000).ConfigureAwait(false);
14                     }
15                     Console.WriteLine($"{DateTime.Now.ToLongTimeString()}\t TaskTest Loop Timeout Stoped");
16                 }, token);
17 
18                 await Task.WhenAll(task1).ConfigureAwait(false);
19             }
20         }
View Code

     也能够CancellationTokenSource.CreateLinkedTokenSource来关联多个CancellationTokenSource

 1 using (CancellationTokenSource cts = new CancellationTokenSource())
 2 {
 3     var cancellationToken = cts.Token;
 4 
 5     using (CancellationTokenSource timeoutSource = new CancellationTokenSource(timeOut * 1000))
 6 
 7     using (CancellationTokenSource linkSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, cancellationToken))
 8     {
 9         var task = Task.Run(() =>
10         {
11             ...
12         }, linkSource.Token);
13 
14         //引起异步异常
15         if (a == 10)
16         {
17             cts.Cancel();
18         }
19 
20         return await task2.ConfigureAwait(true);
21     }
22 }
View Code

 7.TaskCompletionSource

表示未绑定到委托的 Task<TResult> 的制造者方,并经过 Task 属性提供对使用者方的访问。从这个官方解释上看不出这个到底有什么做用,其实这是一种受用者控制建立Task的方式。你可使Task在任何你想要的时候完成,你也能够在任何地方给它一个异常让它失败。这个能够实现事件通知相似的功能;具体就是说TaskCompletionSource若是不进行SetResult或SetException的时候,TaskCompletionSource所委托的的Task是不会有Result,这个Task会一直等待TaskCompletionSource来赋值;这样就极大的简化了异步事件或异步通知的实现。

 1 public static async Task<int> SetValue(int a = 1)
 2 {
 3     var sw = Stopwatch.StartNew();
 4 
 5     TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
 6 
 7     await Task.Run(async () =>
 8     {
 9         try
10         {
11             await Task.Delay(10 * 1000).ConfigureAwait(true);
12             //此处若不赋值,测tcs.Task.Result会一直等待
13             tcs.SetResult(10 / a);
14         }
15         catch (Exception ex)
16         {
17             tcs.SetException(ex);
18         }
19     }).ConfigureAwait(true);
20 
21 
22     return await Task.Run(() =>
23     {
24         int result = 0;
25         try
26         {
27             result = tcs.Task.Result;
28 
29             Console.WriteLine("tcs取值成功!");
30         }
31         catch (AggregateException e)
32         {
33             Console.WriteLine("tcs异常");
34 
35             for (int j = 0; j < e.InnerExceptions.Count; j++)
36             {
37                 Console.WriteLine($"\n-------------------------------------------------\n{e.InnerExceptions[j].ToString()}\n-------------------------------------------------\n");
38             }
39         }
40         sw.Stop();
41         Console.WriteLine($"(ElapsedTime={sw.ElapsedMilliseconds})");
42         return result;
43     }).ConfigureAwait(true);
44 }
View Code

 

 


转载请标明本文来源:http://www.javashuo.com/article/p-ucupckyt-be.html
更多内容欢迎个人的github:https://github.com/yswenli若是发现本文有什么问题和任何建议,也随时欢迎交流~

相关文章
相关标签/搜索