走进异步编程的世界 - 剖析异步方法(上)
走进异步编程的世界 - 剖析异步方法(上)
序 |
这是上篇《走进异步编程的世界 - 开始接触 async/await 异步编程》(入门)的第二章内容,主要是与大家共同深入探讨下异步方法。
本文要求了解委托的使用。
目录
介绍异步方法
图1 异步方法的简单结构图
关于 async 关键字:
①在返回类型之前包含 async 关键字
②它只是标识该方法包含一个或多个 await 表达式,即,它本身不创建异步操作。
③它是上下文关键字,即可作为变量名。
现在先来简单分析一下这三种返回值类型:void、Task 和 Task<T>
(1)Task<T>:调用方法要从调用中获取一个 T 类型的值,异步方法的返回类型就必须是Task<T>。调用方法从 Task 的 Result 属性获取的就是 T 类型的值。

1 private static void Main(string[] args)
2 {
3 Task<int> t = Calculator.AddAsync(1, 2);
4
5 //一直在干活
6
7 Console.WriteLine($"result: {t.Result}");
8
9 Console.Read();
10 }

1 internal class Calculator
2 {
3 private static int Add(int n, int m)
4 {
5 return n + m;
6 }
7
8 public static async Task<int> AddAsync(int n, int m)
9 {
10 int val = await Task.Run(() => Add(n, m));
11
12 return val;
13 }
14 }

图2

图3
(2)Task:调用方法不需要从异步方法中取返回值,但是希望检查异步方法的状态,那么可以选择可以返回 Task 类型的对象。不过,就算异步方法中包含 return 语句,也不会返回任何东西。

1 private static void Main(string[] args)
2 {
3 Task t = Calculator.AddAsync(1, 2);
4
5 //一直在干活
6
7 t.Wait();
8 Console.WriteLine("AddAsync 方法执行完成");
9
10 Console.Read();
11 }

1 internal class Calculator
2 {
3 private static int Add(int n, int m)
4 {
5 return n + m;
6 }
7
8 public static async Task AddAsync(int n, int m)
9 {
10 int val = await Task.Run(() => Add(n, m));
11 Console.WriteLine($"Result: {val}");
12 }
13 }

图4

图5
(3)void:调用方法执行异步方法,但又不需要做进一步的交互。

1 private static void Main(string[] args)
2 {
3 Calculator.AddAsync(1, 2);
4
5 //一直在干活
6
7 Thread.Sleep(1000); //挂起1秒钟
8 Console.WriteLine("AddAsync 方法执行完成");
9
10 Console.Read();
11 }

1 internal class Calculator
2 {
3 private static int Add(int n, int m)
4 {
5 return n + m;
6 }
7
8 public static async void AddAsync(int n, int m)
9 {
10 int val = await Task.Run(() => Add(n, m));
11 Console.WriteLine($"Result: {val}");
12 }
13 }

图6

图7
一、控制流


二、await 表达式
await 表达式指定了一个异步执行的任务。默认情况,该任务在当前线程异步执行。
每一个任务就是一个 awaitable 类的实例。awaitable 类型指包含 GetAwaiter() 方法的类型。
实际上,你并不需要构建自己的 awaitable,一般只需要使用 Task 类,它就是 awaitable。
最简单的方式是在方法中使用 Task.Run() 来创建一个 Task。【注意】它是在不同的线程上执行方法。
让我们一起来看看示例。

1 internal class Program
2 {
3 private static void Main(string[] args)
4 {
5 var t = Do.GetGuidAsync();
6 t.Wait();
7
8 Console.Read();
9 }
10
11
12 private class Do
13 {
14 /// <summary>
15 /// 获取 Guid
16 /// </summary>
17 /// <returns></returns>
18 private static Guid GetGuid() //与Func<Guid> 兼容
19 {
20 return Guid.NewGuid();
21 }
22
23 /// <summary>
24 /// 异步获取 Guid
25 /// </summary>
26 /// <returns></returns>
27 public static async Task GetGuidAsync()
28 {
29 var myFunc = new Func<Guid>(GetGuid);
30 var t1 = await Task.Run(myFunc);
31
32 var t2 = await Task.Run(new Func<Guid>(GetGuid));
33
34 var t3 = await Task.Run(() => GetGuid());
35
36 var t4 = await Task.Run(() => Guid.NewGuid());
37
38 Console.WriteLine($"t1: {t1}");
39 Console.WriteLine($"t2: {t2}");
40 Console.WriteLine($"t3: {t3}");
41 Console.WriteLine($"t4: {t4}");
42 }
43 }
44 }

图2-1

图2-2
上面 4 个 Task.Run() 都是采用了 Task Run(Func<TReturn> func) 形式来直接或间接调用 Guid.NewGuid()。
Task.Run() 支持 4 中不同的委托类型所表示的方法:Action、Func<TResult>、Func<Task> 和 Func<Task<TResult>>

1 internal class Program
2 {
3 private static void Main(string[] args)
4 {
5 var t = Do.GetGuidAsync();
6 t.Wait();
7
8 Console.Read();
9 }
10
11 private class Do
12 {
13 public static async Task GetGuidAsync()
14 {
15 await Task.Run(() => { Console.WriteLine(Guid.NewGuid()); }); //Action
16
17 Console.WriteLine(await Task.Run(() => Guid.NewGuid())); //Func<TResult>
18
19 await Task.Run(() => Task.Run(() => { Console.WriteLine(Guid.NewGuid()); })); //Func<Task>
20
21 Console.WriteLine(await Task.Run(() => Task.Run(() => Guid.NewGuid()))); //Func<Task<TResult>>
22 }
23 }
24 }

图2-3 Task.Run() 方法的重载
三、How 取消异步操作
CancellationToken 和 CancellationTokenSource 这两个类允许你终止执行异步方法。
(1)CancellationToken 对象包含任务是否被取消的信息;如果该对象的属性 IsCancellationRequested 为 true,任务需停止操作并返回;该对象操作是不可逆的,且只能使用(修改)一次,即该对象内的 IsCancellationRequested 属性被设置后,就不能改动。
(2)CancellationTokenSource 可创建 CancellationToken 对象,调用 CancellationTokenSource 对象的 Cancel 方法,会使该对象的 CancellationToken 属性 IsCancellationRequested 设置为 true。
【注意】调用 CancellationTokenSource 对象的 Cancel 方法,并不会执行取消操作,而是会将该对象的 CancellationToken 属性 IsCancellationRequested 设置为 true。
示例

1 internal class Program
2 {
3 private static void Main(string[] args)
4 {
5 CancellationTokenSource source = new CancellationTokenSource();
6 CancellationToken token = source.Token;
7
8 var t = Do.ExecuteAsync(token);
9
10 //Thread.Sleep(3000); //挂起 3 秒
11 //source.Cancel(); //传达取消请求
12
13 t.Wait(token); //等待任务执行完成
14 Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}");
15
16 Console.Read();
17 }
18
19
20 }
21
22 internal class Do
23 {
24 /// <summary>
25 /// 异步执行
26 /// </summary>
27 /// <param name="token"></param>
28 /// <returns></returns>
29 public static async Task ExecuteAsync(CancellationToken token)
30 {
31 if (token.IsCancellationRequested)
32 {
33 return;
34 }
35
36 await Task.Run(() => CircleOutput(token), token);
37 }
38
39 /// <summary>
40 /// 循环输出
41 /// </summary>
42 /// <param name="token"></param>
43 private static void CircleOutput(CancellationToken token)
44 {
45 Console.WriteLine($"{nameof(CircleOutput)} 方法开始调用:");
46
47 const int num = 5;
48 for (var i = 0; i < num; i++)
49 {
50 if (token.IsCancellationRequested) //监控 CancellationToken
51 {
52 return;
53 }
54
55 Console.WriteLine($"{i + 1}/{num} 完成");
56 Thread.Sleep(1000);
57 }
58 }
59 }

图3-1

图3-2 注释两行代码

图3-3:图3-1和图3-2的执行结果(注释两行代码)
上图是不调用 Cancel() 方法的结果图,不会取消任务的执行。
下图在 3 秒后调用 Cancel() 方法取消任务的执行:

图3-4:去掉注释

图3-5:图3-1和图3-4的执行结果(去掉注释)
小结
- 介绍异步方法的语法、三种不同的返回值类型(void、Task 和 Task<T>)和控制流程等。
- 简单常用的异步执行方式:Task.Run()。【注意】它是在不同的线程上执行方法。
- 如何取消异步操作。
传送门
补充篇:《走进异步编程的世界 - 剖析异步方法(下)》
GUI 篇:《走进异步编程的世界 - 在 GUI 中执行异步操作》

浙公网安备 33010602011771号