C#异步介绍

huuhghhgyg avatar
  • huuhghhgyg
  • 5 min read
C#异步介绍

创建多线程?太麻烦了!来试试更快更便捷的异步。程序无须按照代码顺序自上而下的执行。

什么是异步编程

什么是异步编程呢?举个简单的例子:

 1using System.Net.Http;
 2using System.Threading.Tasks;
 3using static System.Console;
 4
 5namespace Core
 6{
 7    class Async
 8    {
 9        static void Main()
10        {
11            Start();
12            End();
13        }
14
15        static void Wait()=>WriteLine("waiting...");
16        static void End()=>WriteLine("end...");
17        static int Start()
18        {
19            WriteLine("start...");
20            HttpClient client = new HttpClient();
21            Waiting();
22            var result = client.GetStringAsync("https://www.visualstudio.com/");
23            string str = result.Result;
24            return str.Length;
25        }
26    }
27}

上面这段代码中,Main方法中的代码是按照自上而下的顺序执行的。网络状况不佳时,Start() 方法是比较耗时 (注意,这里在Start方法中调用了异步方法GetStringAsync,但该方法在此处是以同步方式执行的,具体原因下文会进行说明) ,在 Start() 方法执行完毕之前,整个程序处于阻塞状态。而异步编程可以很好的解决这个问题,一句简单的话来概括异步编程就是,程序无须按照代码顺序自上而下的执行。

async/await

C#5.0新增了async和await关键字,使用这两个关键字可以大大简化异步编程

使用 async 关键字可将方法、lambda 表达式匿名方法标记为异步,即,方法中应该包含一个或多个await表达式,但async关键字本身不会创建异步操作。

1public async Task Asy()
2{
3  //do something...
4}

这里需要注意一点,若使用async关键字标记的方法中没有使用await关键字(编译器会给出警告但不报错),那么该方法将会以同步方式执行。

定义异步方法的几点要求

定义一个异步方法应满足以下几点:

  • 使用async关键字来修饰方法
  • 在异步方法中使用await关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行
  • 尽量不使用void作为返回类型,若希望异步方法返回void类型,请使用Task
  • 异步方法名称以Async结尾
  • 异步方法中不能声明使用 refout 关键字修饰的变量

下面定义一个异步方法StartAsync():

1static async Task<int> StartAsync()
2{
3    HttpClient client = new HttpClient();
4    var str = await client.GetStringAsync("https://www.visualstudio.com/");
5    return str.Length;
6}

异步返回类型

  • Task(T) 返回类型
  • 任务返回类型
  • Void 返回类型
  • 通用的异步返回类型和 ValueTask

异步方法可以具有以下返回类型:

  • Task(对于返回值的异步方法)。

  • Task(对于执行操作但不返回任何值的异步方法)。

  • void(对于事件处理程序)。

  • C#7 开始,任何具有可访问的 GetAwaiter 方法的类型。 GetAwaiter 方法返回的对象必须实现 System.Runtime.CompilerServices.ICriticalNotifyCompletion 接口。

有关异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)

在以下其中一节检查每个返回类型,且在本主题末尾可以找到使用全部三种类型的完整示例。

Task(T) 返回类型

Task 返回类型用于某种异步方法,此异步方法包含 return (C#) 语句,其中操作数具有类型 TResult

在下面的示例中,GetLeisureHours 异步方法包含返回整数的 return 语句。 因此,该方法声明必须指定 Task<int> 的返回类型。 FromResult 异步方法是返回字符串的操作的占位符。

 1using System;
 2using System.Linq;
 3using System.Threading.Tasks;
 4
 5public class Example
 6{
 7   public static void Main()
 8   {
 9      Console.WriteLine(ShowTodaysInfo().Result);
10   }
11
12   private static async Task<string> ShowTodaysInfo()
13   {
14      string ret = $"Today is {DateTime.Today:D}\n" +
15                   "Today's hours of leisure: " +
16                   $"{await GetLeisureHours()}";
17      return ret;
18   }
19
20   static async Task<int> GetLeisureHours()  
21   {  
22       // Task.FromResult is a placeholder for actual work that returns a string.  
23       var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());  
24     
25       // The method then can process the result in some way.  
26       int leisureHours;  
27       if (today.First() == 'S')  
28           leisureHours = 16;  
29       else  
30           leisureHours = 5;  
31     
32       return leisureHours;  
33   }  
34}
35// The example displays output like the following:
36//       Today is Wednesday, May 24, 2017
37//       Today's hours of leisure: 5
38// </Snippet >

ShowTodaysInfo 方法中从 await 表达式内调用 GetLeisureHours 时,await 表达式检索存储在由 GetLeisureHours 方法返回的任务中的整数值(leisureHours 的值)。 有关 await 表达式的详细信息,请参阅 await

通过从应用程序 await 中分离对 GetLeisureHours 的调用,你可以更好地了解此操作,如下面的代码所示。 对非立即等待的方法 TaskOfT_MethodAsync 的调用返回 Task<int>,正如你从方法声明预料的一样。 该任务指派给示例中的 integerTask 变量。 因为 integerTaskTask,所以它包含类型 TResultResult 属性。 在这种情况下,TResult 表示整数类型。 await 应用于 integerTask,await 表达式的计算结果为 integerTaskResult 属性内容。 此值分配给 result2 变量。

重要:Result 属性为阻止属性。 如果你在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻止,直到任务完成且值为可用。 在大多数情况下,应通过使用 await 访问此值,而不是直接访问属性。 上一示例通过检索 Result 属性的值来阻止主线程,从而使 ShowTodaysInfo 方法可在应用程序结束之前完成执行。

1var infoTask = GetLeisureHours();
2
3// You can do other work that does not rely on integerTask before awaiting.
4
5string ret = $"Today is {DateTime.Today:D}\n" +
6             "Today's hours of leisure: " +
7             $"{await infoTask}";

任务返回类型

不包含 return 语句的异步方法或包含不返回操作数的 return 语句的异步方法通常具有返回类型 Task。 如果此类方法同步运行,它们将返回 void。 如果在异步方法中使用 Task 返回类型,调用方法可以使用 await 运算符暂停调用方的完成,直至被调用的异步方法结束。

如下示例中,WaitAndApologize 异步方法不包含 return 语句,因此此方法返回 Task 对象。 通过这样可等待 WaitAndApologize。 请注意,Task 类型不包含 Result 属性,因为它不具有任何返回值。

 1using System;
 2using System.Threading.Tasks;
 3
 4public class Example
 5{
 6   public static void Main()
 7   {
 8      DisplayCurrentInfo().Wait();
 9   }
10
11   static async Task DisplayCurrentInfo()
12   {
13      await WaitAndApologize();
14      Console.WriteLine($"Today is {DateTime.Now:D}");
15      Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
16      Console.WriteLine("The current temperature is 76 degrees.");
17   }
18
19   static async Task WaitAndApologize()
20   {
21      // Task.Delay is a placeholder for actual work.  
22      await Task.Delay(2000);  
23      // Task.Delay delays the following line by two seconds.  
24      Console.WriteLine("\nSorry for the delay. . . .\n");  
25   }
26}
27// The example displays the following output:
28//       Sorry for the delay. . . .
29//       
30//       Today is Wednesday, May 24, 2017
31//       The current time is 15:25:16.2935649
32//       The current temperature is 76 degrees.

通过使用 await 语句而不是 await 表达式等待 WaitAndApologize,类似于返回 void 的同步方法的调用语句。 Await 运算符的应用程序在这种情况下不生成值。

如同上一个 Task 示例,可以从 await 运算符的应用程序中分离对 Task_MethodAsync 的调用,如以下代码所示。 但是,请记住,Task 没有 Result 属性,并且当 await 运算符应用于 Task 时不产生值。

以下代码将调用 WaitAndApologize 方法和等待此方法返回的任务分离。

1Task wait = WaitAndApologize();
2
3string output = $"Today is {DateTime.Now:D}\n" + 
4                $"The current time is {DateTime.Now.TimeOfDay:t}\n" +
5                $"The current temperature is 76 degrees.\n";
6await wait;
7Console.WriteLine(output);

Void 返回类型

在异步事件处理程序中使用 void 返回类型,这需要 void 返回类型。 对于事件处理程序以外的不返回值的方法,应返回 Task,因为无法等待返回 void 的异步方法。 这种方法的任何调用方必须能够继续完成,而无需等待调用的异步方法完成,并且调用方必须独立于异步方法生成的任何值或异常。

返回 void 的异步方法的调用方无法捕获从该方法引发的异常,且此类未经处理的异常可能会导致应用程序故障。 如果返回 TaskTask 的异步方法中出现异常,此异常将存储于返回的任务中,并在等待该任务时再次引发。 因此,请确保可以产生异常的任何异步方法都具有返回类型 TaskTask,并确保会等待对方法的调用。

有关如何在异步方法中捕捉异常的更多信息,请参阅 try-catch

以下示例定义一个异步事件处理程序。

 1using System;
 2using System.Threading.Tasks;
 3
 4public class Counter
 5{
 6   private int threshold = 0;
 7   private int iterations = 0;
 8   private int ctr = 0;
 9
10   event EventHandler<EventArgs> ThresholdReached;
11
12   public Counter(int threshold)
13   {
14      this.threshold = threshold;
15      ThresholdReached += thresholdReachedEvent;
16   }
17
18   public async Task<int> StartCounting(int limit)
19   {
20      iterations = 1;
21      for (int index = 0; index <= limit; index++) {
22         if (ctr == threshold)
23            thresholdReachedEvent(this, EventArgs.Empty);
24         ctr++;
25         await Task.Delay(500);
26      }
27      int retval = ctr + (iterations - 1) * threshold;
28      Console.WriteLine($"On iteration {iterations}, reached {limit}");
29      return retval;    
30   }
31
32   async void thresholdReachedEvent(object sender, EventArgs e)
33   {
34       Console.WriteLine($"Reached {ctr}. Resetting...");
35       await Task.Delay(1000);
36       ctr = 0;
37       iterations++;
38   }
39}
40
41public class Example
42{
43   public static void Main()
44   {
45      RunCounter().Wait();
46   }
47
48   private static async Task RunCounter()
49   {
50      var count = new Counter(5);
51      await count.StartCounting(8);
52   }
53}

通用的异步返回类型和 ValueTask

从 C# 7 开始,异步方法可返回任何具有可访问的 GetAwaiter 方法的类型。 TaskTask 是引用类型,因此,性能关键路径中的内存分配会对性能产生负面影响,尤其当分配出现在紧凑循环中时。 支持通用返回类型意味着可返回轻量值类型(而不是引用类型),从而避免额外的内存分配。

.NET 提供 System.Threading.Tasks.ValueTask 结构作为返回任务的通用值的轻量实现。 要使用 System.Threading.Tasks.ValueTask 类型,必须向项目添加 System.Threading.Tasks.Extensions NuGet 包。 如下示例使用 ValueTask 结构检索两个骰子的值。

 1using System;
 2using System.Threading.Tasks;
 3
 4class Program
 5{
 6   static Random rnd;
 7   
 8   static void Main()
 9   {
10      Console.WriteLine($"You rolled {GetDiceRoll().Result}");
11   }
12   
13   private static async ValueTask<int> GetDiceRoll()
14   {
15      Console.WriteLine("...Shaking the dice...");
16      int roll1 = await Roll();
17      int roll2 = await Roll();
18      return roll1 + roll2; 
19   } 
20   
21   private static async ValueTask<int> Roll()
22   {
23      if (rnd == null)
24         rnd = new Random();
25      
26      await Task.Delay(500);
27      int diceRoll = rnd.Next(1, 7);
28      return diceRoll;
29   } 
30}
31// The example displays output like the following:
32//       ...Shaking the dice...
33//       You rolled 8

请参阅

FromResult

演练:使用 Async 和 Await 访问 Web (C#)

异步程序中的控制流 (C#)

Async await

huuhghhgyg

Writter by : huuhghhgyg

Never Settle

Recommended for You

HCalculator

HCalculator