zoukankan      html  css  js  c++  java
  • 异步编程基础

    >>返回《C# 并发编程》

    1. 概述

    前面的文章介绍了标识了 asyncawait 的代码,是怎么被线程执行的。

    >>同步上下文-7.5 异步编程(Async)

    下面介绍一些类库和常用的API

    2. 报告进度

    使用 IProgress<T>Progress<T> 类型

    1. 构造 Progress<T> 实例时捕获当前 同步上下文 实例;
    2. Progress<T> 实例的ProgressChanged 事件被调用时使用上面捕获的同步上下文
    3. 如果在执行构造函数的线程没有同步上下文时(隐含使用的Default同步上下文),则将在 ThreadPool 中调用事件
    static async Task DoProgressAsync(int count, IProgress<int> progress = null)
    {
        for (int i = 0; i < count; i++)
        {
            await Task.Delay(200);
            if (progress != null)
                progress.Report(i + 1);
        }
    }
    
    static async Task CallProgressAsync()
    {
        int count = 5;
        var progress = new Progress<int>();
        progress.ProgressChanged += (sender, args) =>
        {
            System.Console.WriteLine($"{args}/{count}");
        };
        await DoProgressAsync(count, progress);
    }
    

    输出为:

    1/5
    2/5
    3/5
    4/5
    5/5
    

    3. 等待一组任务完成

    Task.WhenAll 方法有以 IEnumerable 类型作为参数的重载,但建议大家不要使用。

    • 调用 ToListToArray 方法后,序列中没有启动的任务就开始了
    static async Task DownloadAllAsync()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        IEnumerable<string> urls = new string[]{
                    "https://www.baidu.com/",
                    "https://cn.bing.com/"
                };
        var httpClient = new HttpClient();
        // 定义每一个 url 的使用方法。
        var downloads = urls.Select(url =>
        {
            Console.WriteLine($"{url}:start");
            var res = httpClient.GetStringAsync(url);
            res.ContinueWith(t => Console.WriteLine($"{url}:{sw.ElapsedMilliseconds}ms"));
            return res;
        });
        // 注意,到这里,序列还没有求值,所以所有任务都还没真正启动。
    
        // 下面,所有的 URL 下载同步开始。
        Task<string>[] downloadTasks = downloads.ToArray();
        // 到这里,所有的任务已经开始执行了。
        Console.WriteLine($"await Task.WhenAll");
        // 用异步方式等待所有下载完成。
        string[] htmlPages = await Task.WhenAll(downloadTasks);
    
        Console.WriteLine($"jobs done.");
    }
    

    输出为:

    https://www.baidu.com/:start
    https://cn.bing.com/:start
    await Task.WhenAll
    https://www.baidu.com/:270ms
    jobs done.
    ; 由于返回的是请求的 Task 不是 ContinueWith 的打印 Task
    https://cn.bing.com/:1089ms
    

    4. 异常处理

    • 如果有一个任务抛出异常,则 Task.WhenAll 会出错,并把这个异常放在返回的 Task
    • 如果多个任务抛出异常,则这些异常都会放在返回的 Task
    • 如果这个 Task 在被 await 调用,就只会抛出该异步方法的一个异常
    • 如果要得到每个异常,可以检查 Task.WhenAll 返回的 TaskException 属性:

    示例:

    static async Task ThrowNotImplementedExceptionAsync()
    {
        await Task.Delay(10);
        throw new NotImplementedException();
    }
    
    static async Task<int> ThrowInvalidOperationExceptionAsync()
    {
        TaskCompletionSource<int> completionSource = new TaskCompletionSource<int>();
        completionSource.TrySetException(new InvalidOperationException());
        return await completionSource.Task;
    }
    static async Task ObserveOneExceptionAsync()
    {
        System.Console.WriteLine("OneException");
        var task1 = ThrowNotImplementedExceptionAsync();
        var task2 = ThrowInvalidOperationExceptionAsync();
        try
        {
            await Task.WhenAll(task1, task2);
        }
        catch (Exception ex)
        {
            // ex 要么是 NotImplementedException,要么是 InvalidOperationException
            System.Console.WriteLine(ex.GetType().Name);
        }
    }
    static async Task ObserveAllExceptionsAsync()
    {
        System.Console.WriteLine("AllExceptions");
        var task1 = ThrowNotImplementedExceptionAsync();
        var task2 = ThrowInvalidOperationExceptionAsync();
        Task allTasks = Task.WhenAll(task1, task2);
        try
        {
            await allTasks;
        }
        catch
        {
            AggregateException allExceptions = allTasks.Exception;
            allExceptions.Handle(ex =>
            {
                System.Console.WriteLine(ex.GetType().Name);
                return true;
            });
        }
    }
    

    输出为:

    OneException
    NotImplementedException
    
    AllExceptions
    NotImplementedException
    InvalidOperationException
    

    5. 等待任意一个任务完成

    // 返回第一个响应的 URL 的数据长度。
    private static async Task<int> FirstRespondingUrlAsync()
    {
        string urlA = "https://www.baidu.com/";
        string urlB = "https://cn.bing.com/";
    
        var httpClient = new HttpClient();
        // 并发地开始两个下载任务。
        Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
        Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
        // 等待任意一个任务完成。 
        Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
        // 返回从 URL 得到的数据的长度。 
        byte[] data = await completedTask;
        Console.WriteLine($"Finish: {(completedTask == downloadTaskA ? nameof(downloadTaskA) : nameof(downloadTaskA))}");
        Console.WriteLine($"downloadTaskA: {downloadTaskA.Status}");
        Console.WriteLine($"downloadTaskB: {downloadTaskB.Status}");
        return data.Length;
    }
    

    输出为:

    Finish: downloadTaskA
    downloadTaskA: RanToCompletion
    downloadTaskB: WaitingForActivation
    

    如果这个任务完成时有异常,这个异常也不会传递给 Task.WhenAny 返回的 Task 对象。因此,通常需要在 Task 对象完成后继续使用 await

    第一个任务完成后,考虑是否要取消剩下的任务。如果其他任务没有被取消,也没有被继续 await,那它们就处于被遗弃的状态。被遗弃的任务会继续运行直到完成,它们的结果会被忽略,抛出的任何异常也会被忽略。

    //每个任务需要等到Trace.WriteLine执行完才能执行下一个
    static async Task<int> DelayAndReturnAsync(int val)
    {
        await Task.Delay(TimeSpan.FromSeconds(val));
        return val;
    }
    // 当前,此方法输出“2”,“3”,“1”。 // 我们希望它先输出先完成的,期望 输出“1”,“2”,“3”。 
    static async Task ProcessTasksAsync1()
    {
        // 创建任务队列。
        Task<int> taskA = DelayAndReturnAsync(2);
        Task<int> taskB = DelayAndReturnAsync(3);
        Task<int> taskC = DelayAndReturnAsync(1);
        var tasks = new[] { taskA, taskB, taskC };
        // 按顺序 await 每个任务。 
        foreach (var task in tasks)
        {
            var result = await task;
            Console.WriteLine(result);
        }
    }
    
    
    //不等Trace.WriteLine切任务并行的解决方案
    // 现在,这个方法输出“1”,“2”,“3”。 
    static async Task ProcessTasksAsync2()
    {
        // 创建任务队列。
        Task<int> taskA = DelayAndReturnAsync(2);
        Task<int> taskB = DelayAndReturnAsync(3);
        Task<int> taskC = DelayAndReturnAsync(1);
        var tasks = new[] { taskA, taskB, taskC };
        var processingTasks = tasks.Select(async t =>
        {
            var result = await t;
            Console.WriteLine(result);
        }).ToArray();
        // 等待全部处理过程的完成。
        await Task.WhenAll(processingTasks);
    }
    static async Task ProcessTasksAsyncExe()
    {
        Stopwatch sw = Stopwatch.StartNew();
        System.Console.WriteLine("ProcessTasksAsync1");
        await ProcessTasksAsync1();
        System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
        sw.Restart();
        System.Console.WriteLine();
        System.Console.WriteLine("ProcessTasksAsync2");
        await ProcessTasksAsync2();
        System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
    }
    

    输出为:

    ProcessTasksAsync1
    2
    3
    1
    3007ms
    
    ProcessTasksAsync2
    1
    2
    3
    3004ms
    

    6. 避免上下文延续

    默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行。 如果是 UI上下文 ,并且有大量的 async 方法在 UI上下文 中恢复,就会引起性能上的问题。

    • ConfigureAwait(true) 延续上下文(执行完异步 await ,回到同步上下文)
    • ConfigureAwait(false) 不延续上下文(执行完异步 await ,由于没有记录之前的同步上下文,后续代码在 Default上下文 中运行)

    7. async void

    处理 async void 方法的异常有一个办法:

    • 一个异常从 async void 方法中传递出来时,会在其同步上下文中引发出来
    • async void方法启动时,同步上下文 就处于激活状态
      • 如果系统运行环境有特定的 同步上下文(如:UI同步上下文,ASP.Net同步上下文),通常就可以在全局范围内处理这些顶层的异常
        • PF 有 Application.DispatcherUnhandledException
        • WinRT 有 Application.UnhandledException
        • ASP.NET 有 Application_Error
  • 相关阅读:
    CentOS查看CPU信息、位数、多核信息
    Linux常用命令大全
    chmod命令详细用法
    tar命令的详细解释
    yum和rpm命令详解
    LeetCode 241. Different Ways to Add Parentheses
    LeetCode 139. Word Break
    LeetCode 201. Bitwise AND of Numbers Range
    LeetCode 486. Predict the Winner
    LeetCode 17. Letter Combinations of a Phone Number
  • 原文地址:https://www.cnblogs.com/BigBrotherStone/p/12245488.html
Copyright © 2011-2022 走看看