学海无涯

导航

异步编程基础

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

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

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

 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);
    }

  

 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> 并非专用于异步代码,在耗时较长的同步代码中也可以使用。

 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 无返回结果的任务

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 全部任务都有着相同返回结果类型

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 参数类型重载  

 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  等待多个任务的异常处理  

  async Task ThrowNotimplmentedExceptionAsync()
        {
            throw new NotImplementedException();
        }
        async Task ThrowInvalidOperationExceptionAsync()
        {
            throw new InvalidOperationException();
        }

  

3.4.1 仅捕捉其中的一个异常(推荐)  
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 服务请求股票行情信息,只要有一个请求有响应任务就算完成。

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;
        }

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

 async Task<int> DelayAndReturnAsync(int value)
        {
            await Task.Delay(TimeSpan.FromSeconds(value));
            return value;
        }

错误示范:

  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);
            }
        }

 正确示范:

 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);
        }

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

 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 2023-09-24 15:27  宁静致远.  阅读(10)  评论(0编辑  收藏  举报