异步编程基础

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

  • 1. 概述
  • 2. 报告进度
  • 3. 等待一组任务完成
  • 4. 异常处理
  • 5. 等待任意一个任务完成
  • 6. 避免上下文延续
  • 7. async void

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

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

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

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

如果这个任务完成时有异常,这个异常也不会传递给 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");
}

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

原文地址:https://www.cnblogs.com/BigBrotherStone/p/12245488.html

时间: 2024-11-06 09:48:23

异步编程基础的相关文章

[NodeJS]Node异步编程基础

零.前言 为什么要用Node? Node把非阻塞IO作为提高应用性能的方式.而在JS中,天生拥有着异步编程机制: 事件机制.同时JS中不存在多进程.这样当你执行相对较慢需要花费时间长的IO操作时并不会阻塞主进程的任务. 在NodeJS中流行两种响应逻辑的管理方式: 回调, 事件监听. 回调通常用来定义一次性响应的逻辑.事件监听器本质上也是一个回调,不同的是它跟事件相互关联. 一.使用回调来处理一次性事件 回调是一个函数,被当做参数传递给异步函数,描述了异步操作完成后要做什么. 案例: 创建一个简

异步编程基础知识

异步在IO密集型场景有优势,应用在爬虫场景就是: 原文地址:https://www.cnblogs.com/jameskane/p/8457030.html

[C#] C#并发编程-异步编程基础-报告进度

1 int ProgressRate = 0; 2 3 private async void btnProgress_Click(object sender, EventArgs e) 4 { 5 ProgressRate = 0; 6 7 var progress = new Progress<int>(); 8 9 //progress的进度改变 10 progress.ProgressChanged += (obj1, obj2) => 11 { 12 if (ProgressRa

【读书笔记】C#高级编程 第十三章 异步编程

(一)异步编程的重要性 使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程.有3中不同的异步编程模式:异步模式.基于事件的异步模式和新增加的基于任务的异步模式(TAP,可利用async和await关键字来实现). (二)异步模式 1.C#1的APM 异步编程模型(Asynchronous Programming Model). 2.C#2的EAP 基于事件的异步模式(Event-based Asynchronous Pattern). 3.TAP 基于任务的异步模

Node.js学习笔记【3】NodeJS基础、代码的组织和部署、文件操作、网络操作、进程管理、异步编程

一.表 学生表 CREATE TABLE `t_student` ( `stuNum` int(11) NOT NULL auto_increment, `stuName` varchar(20) default NULL, `birthday` date default NULL, PRIMARY KEY  (`stuNum`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 学生分数表 CREATE TABLE `t_stu_score` ( `id` int(11

C#基础系列——异步编程初探:async和await

前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了异步并行变成带来了很大的方便.异步编程涉及到的东西还是比较多,本篇还是先介绍下async和await的原理及简单实现. C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序列化效率比拼 C#基础系列——反射笔记 C#基础系列——At

C#复习笔记(5)--C#5:简化的异步编程(异步编程的基础知识)

异步编程的基础知识 C#5推出的async和await关键字使异步编程从表面上来说变得简单了许多,我们只需要了解不多的知识就可以编写出有效的异步代码. 在介绍async和await之前,先介绍一些基础的概念: 并发:同时做很多事情. 这个解释直接表明了并发的作用.终端用户程序利用并发功能,在输入数据库的同时响应用户输入.服务器应用利用并发,在处理第一个请求的同时响应第二个请求.只要你希望程序同时做多件事情,你就需要并发.几乎每个软件程序 都会受益于并发. 多线程:并发的一种形式,它采用多个线程来

python网络编程基础(线程与进程、并行与并发、同步与异步)

python网络编程基础(线程与进程.并行与并发.同步与异步) 目录 线程与进程 并行与并发 同步与异步 线程与进程 进程 前言 进程的出现是为了更好的利用CPU资源使到并发成为可能. 假设有两个任务A和B,当A遇到IO操作,CPU默默的等待任务A读取完操作再去执行任务B,这样无疑是对CPU资源的极大的浪费.聪明的老大们就在想若在任务A读取数据时,让任务B执行,当任务A读取完数据后,再切换到任务A执行.注意关键字切换,自然是切换,那么这就涉及到了状态的保存,状态的恢复,加上任务A与任务B所需要的

C#基础提升系列——C#异步编程

C#异步编程 关于异步的概述,这里引用MSDN的一段文字: 异步编程是一项关键技术,使得能够简单处理多个核心上的阻塞 I/O 和并发操作. 如果需要 I/O 绑定(例如从网络请求数据或访问数据库),则需要利用异步编程. 还可以使用 CPU 绑定代码(例如执行成本高昂的计算),对编写异步代码而言,这是一个不错的方案. 异步代码具有以下特点: 等待 I/O 请求返回的同时,可通过生成处理更多请求的线程,处理更多的服务器请求. 等待 I/O 请求的同时生成 UI 交互线程,并通过将长时间运行的工作转换