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
  • 相关阅读:
    基于linux、c的倒排索引
    关于A类,B类,C类IP地址的网段和主机数的计算方法
    如何找出字典中的兄弟单词
    简单验证码识别程序(源码)
    (一)SVM的八股简介
    验证码识别程序
    倒排索引
    验证码识别技术 Captcha Decode Technology
    字符串的组合
    C# 中panel的mousewheel事件触发 (转)
  • 原文地址:https://www.cnblogs.com/BigBrotherStone/p/12245488.html
Copyright © 2011-2022 走看看