学海无涯

导航

统计

异步编程基础

使用 async 和 await 进行异步操作的基础知识,其中只会涉及自然异步操作,如 HTTP 请求、数据库指令、Web 服务调用等。

一、需要通过 异步签名实现同步方法时,返回已完成的任务 

如果在继承异步接口或者基类的同时又想同步实现该任务,便可能发生这样的情况。当需要异步接口的简单签名或模拟对象时,或者当需要对异步代码做单元测试时,这项技术特别有用。

1
2
3
4
5
6
7
8
public interface IMyAsyncInterface
   {
       Task<int> GetValueAsync();
       Task DoSomethingAsync();
       Task<T> NotImplementedAsync<T>();
       Task<int> GetValueAsync(CancellationToken token);
       Task<T> DelayResult<T>(T result, TimeSpan delay);
   }

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MySynchronousImplementation : IMyAsyncInterface
   {
       public async Task<T> DelayResult<T>(T result, TimeSpan delay)
       {//暂停一段时间,当程序需要异步等待一段时间。在进行单元测试或实现重试延迟时,这是一种常见的场景。
           await Task.Delay(delay);
           return result;
       }
 
       public Task DoSomethingAsync()
       {
           //1.异步签名实现同步方法,并且无返回值
           try
           {
               //DoSomethingSynchronously()
               return Task.CompletedTask;
           }
           catch (Exception ex)
           {//同步实现可能会失败,就应当捕捉异常并通过Task.FromExceptiion 来返回它们。
               return Task.FromException(ex);
           }
       }
 
       public Task<int> GetValueAsync()
       {
           //2.异步签名实现同步方法,并且有返回值
           return Task.FromResult(13);
       }
 
       public Task<int> GetValueAsync(CancellationToken token)
       {
           //3.带取消功能的任务
           if (token.IsCancellationRequested)
               return Task.FromCanceled<int>(token);
           return Task.FromResult(13);
       }
 
       public Task<T> NotImplementedAsync<T>()
       {
           //4.需要返回异常报错信息
           return Task.FromException<T>(new NotImplementedException());
       }
   }

 二、实时更新进度

Progress<T> 会在创建时捕获当前上下文,并在此上下文中调用回调函数。即:如果是在 UI 线程中创建的,那么即使使用异步方法在后台线程中调用 Report ,我们也可以在它的回调函数中更新 UI .

IProgress<T>.Report 方法通常是异步,T 最好定义为值类型。

IProgress<T> 并非专用于异步代码,在耗时较长的同步代码中也可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public async Task MyMethodAsync(IProgress<double> progress = null)
       {
           double percentComlete = 0;
           do
           {
               percentComlete += 2;
               progress?.Report(percentComlete);//报告进度
           }
           while (percentComlete < 100);
       }
       public async Task CallMyMethodAsync()
       {
           var progress = new Progress<double>();
           progress.ProgressChanged += (sender, e) =>
           {
               Console.WriteLine(e.ToString());
           };
           await MyMethodAsync(progress);
       }

 运行结果:

 三、等待多个任务完成

场景:有多个任务,需要等等他们全部完成。

3.1 无返回结果的任务

1
2
3
4
Task task1 = Task.Delay(TimeSpan.FromSeconds(1));
Task task2 = Task.Delay(TimeSpan.FromSeconds(2));
Task task3 = Task.Delay(TimeSpan.FromSeconds(3));
 await Task.WhenAll(task1, task2, task3);

3.2 全部任务都有着相同返回结果类型

1
2
3
4
5
6
7
8
9
Task<int> task1 = Task.FromResult(3);
            Task<int> task2 = Task.FromResult(5);
            Task<int> task3 = Task.FromResult(7);
 
            int[] results = await Task.WhenAll(task1, task2, task3);
            foreach (int result in results)
            {
                Console.WriteLine(result.ToString());
            }

运行结果:3,5,7

3.3.  Task.WhenAll() 的 IEnumerable 参数类型重载  

1
2
3
4
5
6
7
8
9
public async Task<string> DownloadAllAsync(HttpClient httpClient, IEnumerable<string> urls)
       {
           //为每个 URL 定义请求操作
           var downloads = urls.Select(url => httpClient.GetStringAsync(url));
           Task<string>[] downloadTasks = downloads.ToArray();
           //异步等待所有下载任务完成,并返回结果
           string[] htmlPages = await Task.WhenAll(downloadTasks);
           return string.Concat(htmlPages);
       }

3.4  等待多个任务的异常处理  

1
2
3
4
5
6
7
8
async Task ThrowNotimplmentedExceptionAsync()
      {
          throw new NotImplementedException();
      }
      async Task ThrowInvalidOperationExceptionAsync()
      {
          throw new InvalidOperationException();
      }

  

3.4.1 仅捕捉其中的一个异常(推荐)  
1
2
3
4
5
6
7
8
9
10
11
12
13
public async Task ObserveOneExceptionAsync()
        {
            var task1=ThrowNotimplmentedExceptionAsync();
            var task2=ThrowInvalidOperationExceptionAsync();
            try
            {
                await Task.WhenAll(task1, task2);
            }
            catch (Exception ex)
            {//仅能捕捉一个任务的异常
                //ex 不是 NotImplementedException 就是 InvalidOperationException
            }
        }
3.4.2 捕捉全部异常
复制代码
 public async Task ObserveAllExceptionsAsync()
        {
            var task1=ThrowNotimplmentedExceptionAsync();
            var task2=ThrowInvalidOperationExceptionAsync();
            Task allTasks= Task.WhenAll(task1, task2);
            try
            {
                await allTasks;
            }
            catch
            {//捕捉全部任务的异常
                AggregateException allExceptions = allTasks.Exception;
            }
        }
复制代码

 四、仅等待其中任意一个任务完成

场景:针对同一个操作进行多次独立尝试,并且一次成功即可算作任务完成。比如:同时从多个 Web 服务请求股票行情信息,只要有一个请求有响应任务就算完成。

1
2
3
4
5
6
7
8
9
10
11
public async Task<int> FirstRespondingUrlAsync(HttpClient client, string urlA, string urlB)
        {
            //设置两个下载任务
            Task<byte[]> downloadTaskA = client.GetByteArrayAsync(urlA);
            Task<byte[]> downloadTaskB = client.GetByteArrayAsync(urlB);
            //等待两个任务中的任务一个任务完成,即可得到结果
            Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
            //返回从 URL 中获取的数据长度
            byte[] result = await completedTask;
            return result.Length;
        }

五、等待全部任务完成,同时 其中任意一个任务完成时,实时处理相关结果

1
2
3
4
5
async Task<int> DelayAndReturnAsync(int value)
       {
           await Task.Delay(TimeSpan.FromSeconds(value));
           return value;
       }

错误示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public async Task ProcessTasksAsync()
      {//1.按任务在列表中的顺序依次执行,即:执行顺序为:task1->task2->task3 。(不推荐)
          //打印结果:2,3,1,理想的打印结果:1,2,3,因为 task3 只等待1秒,最先完成。
          Task<int> task1 = DelayAndReturnAsync(2);
          Task<int> task2 = DelayAndReturnAsync(3);
          Task<int> task3 = DelayAndReturnAsync(1);
 
          Task<int>[] tasks = new[] { task1, task2, task3 };
          foreach (var task in tasks)
          {//按顺序等待每个任务
              var result=await task;
              Trace.WriteLine(result);
          }
      }

 正确示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
async Task AwaitAndProcessAsync(Task<int> task)
       {
           int result=await task;
           Trace.WriteLine(result);//处理当前已完成的任务结果
       }
       public async Task ProcessTasksAsync2()
       {//2.1 任务完成时,实时处理结果,与任务在列表中的顺序无关。(推荐)
           //打印结果:1,2,3
           Task<int> task1 = DelayAndReturnAsync(2);
           Task<int> task2 = DelayAndReturnAsync(3);
           Task<int> task3 = DelayAndReturnAsync(1);
 
           Task<int>[] tasks = new[] { task1, task2, task3 };
            
           IEnumerable<Task> taskQuery=from t in tasks select AwaitAndProcessAsync(t);
 
           Task[] processingTasks=taskQuery.ToArray();
           //等待全部任务完成
           await Task.WhenAll(processingTasks);
       }
 
       public async Task ProcessTasksAsync3()
       {//2.2 任务完成时,实时处理结果,与任务在列表中的顺序无关。(可选)
           //打印结果:1,2,3
           Task<int> task1 = DelayAndReturnAsync(2);
           Task<int> task2 = DelayAndReturnAsync(3);
           Task<int> task3 = DelayAndReturnAsync(1);
 
           Task<int>[] tasks = new[] { task1, task2, task3 };
 
           Task[] processingTasks =tasks.Select(async t =>
           {
               var result=await t;
               Trace.WriteLine(result);//处理当前已完成的任务结果
           }).ToArray();
           //等待全部任务完成
           await Task.WhenAll(processingTasks);
       }

六、当任务完成时,不需要恢复上下文 

1
2
3
4
5
6
7
8
9
10
11
12
async Task ResumeOnContextAsync()
       {
           await Task.Delay(TimeSpan.FromSeconds(1));
           //当任务执行完成时,继续执行方法时恢复上下文
           //dosomething...
       }
       async Task ResumeWithoutContextAsync()
       {
           await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
           //当任务执行完成时,继续执行方法时,已丢弃上下文
           //dosomething...
       }

当 async 在 await 之后恢复时,它默认会在主线程当前上下文中继续执行。如果是在UI主线程,并且有很多 async 方法时,可能会引发性能问题。  

建议:在UI主线程中,每秒上百个尚可,每秒上千个则过多。

最好从一开始就避免这个问题,针对任意无须在原始上下文中恢复的 async 方法,调用 configureAwait(false) 方法。这样做不会产生任何负作用。

如果有一个 async 方法,其中部分需要上下文,分部不需要上下文,那么可以考虑将其拆分成两个或多个 async 方法,这样在组织代码时,层次会更为清晰。

 

 

  

 

  

 

 

posted on   宁静致远.  阅读(13)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
历史上的今天:
2022-09-24 安装项目模板IdentityServer4.Templates
2022-09-24 客户端URL路径组织模式
2022-09-24 Service模块读取AppSettings.json
2022-09-24 使用ConnectionMultiplexer访问Redis
点击右上角即可分享
微信分享提示