返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >c# 几个常见的TAP异步操作
  • 312
分享到

c# 几个常见的TAP异步操作

2024-04-02 19:04:59 312人浏览 薄情痞子
摘要

目录1 任务状态手动控制任务启动确保任务已激活2 任务取消3 进度报告4 Task.Yield 让步5 定制异步任务后续操作ConfigureAwaitContinueWith6 总

在本系列上一篇文章 [15:异步编程基础] 中,我们讲到,现代应用程序广泛使用的是基于任务的异步编程模式(TAP),历史的 EAP 和 AMP 模式已经过时不推荐使用。今天继续总结一下 TAP 的异步操作,比如取消任务、报告进度、Task.Yield()、ConfigureAwait() 和并行操作等。

虽然实际 TAP 编程中很少使用到任务的状态,但它是很多 TAP 操作机理的基础,所以下面先从任务状态讲起。

1 任务状态

Task 类为异步操作提供了一个生命周期,这个周期由 TaskStatus 枚举表示,它有如下值:


public enum TaskStatus
{
    Created = 0,
    WaitingForActivation = 1,
    WaitingToRun = 2,
    Running = 3,
    WaitingForChildrenToComplete = 4,
    RanToCompletion = 5,
    Canceled = 6,
    Faulted = 7
}

其中 Canceled、Faulted 和 RanToCompletion 状态一起被认为是任务的最终状态。因此,如果任务处于最终状态,则其 IsCompleted 属性为 true 值。

手动控制任务启动

为了支持手动控制任务启动,并支持构造与调用的分离,Task 类提供了一个 Start 方法。由 Task 构造函数创建的任务被称为冷任务,因为它们的生命周期处于 Created 状态,只有该实例的 Start 方法被调用才会启动。

任务状态平时用的情况不多,一般我们在封装一个任务相关的方法时,可能会用到。比如下面这个例子,需要判断某任务满足一定条件才启动:


static void Main(string[] args)
{
    MyTask t = new(() =>
    {
        // do something.
    });

    StartMyTask(t);

    Console.ReadKey();
}

public static void StartMyTask(MyTask t)
{
    if (t.Status == TaskStatus.Created && t.Counter>10)
    {
        t.Start();
    }
    else
    {
        // 这里模拟计数,直到 Counter>10 再执行 Start
        while (t.Counter <= 10)
        {
            // Do something
            t.Counter++;
        }
        t.Start();
    }
}

public class MyTask : Task
{
    public MyTask(Action action) : base(action)
    {
    }

    public int Counter { get; set; }
}

同样,TaskStatus.Created 状态以外的状态,我们叫它热任务,热任务一定是被调用了 Start 方法激活过的。

确保任务已激活

注意,所有从 TAP 方法返回的任务都必须被激活,比如下面这样的代码:


MyTask task = new(() =>
{
    Console.WriteLine("Do something.");
});

// 在其它地方调用
await task;

在 await 之前,任务没有执行 Task.Start 激活,await 时程序就会一直等待下去。所以如果一个 TAP 方法内部使用 Task 构造函数来实例化要返回的 Task,那么 TAP 方法必须在返回 Task 对象之前对其调用 Start。

2 任务取消

在 TAP 中,取消对于异步方法实现者和消费者来说都是可选的。如果一个操作允许取消,它就会暴露一个异步方法的重载,该方法接受一个取消令牌(CancellationToken 实例)。按照惯例,参数被命名为 cancellationToken。例如:


public Task ReadAsync(
    byte [] buffer, int offset, int count,
    CancellationToken cancellationToken)

异步操作会监控这个令牌是否有取消请求。如果收到取消请求,它可以选择取消操作,如下面的示例通过 while 来监控令牌的取消请求:


static void Main(string[] args)
{
    CancellationTokenSource source = new();
    CancellationToken token = source.Token;

    var task = DoWork(token);

    // 实际情况可能是在稍后的其它线程请求取消
    Thread.Sleep(100);
    source.Cancel();

    Console.WriteLine($"取消后任务返回的状态:{task.Status}");

    Console.ReadKey();
}

public static Task DoWork(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        // Do something.
        Thread.Sleep(1000);

        return Task.CompletedTask;
    }
    return Task.FromCanceled(cancellationToken);
}

如果取消请求导致工作提前结束,甚至还没有开始就收到请求取消,则 TAP 方法返回一个以 Canceled 状态结束的任务,它的 IsCompleted 属性为 true,且不会抛出异常。当任务在 Canceled 状态下完成时,任何在该任务注册的延续任务仍都会被调用和执行,除非指定了诸如 NotOnCanceled 这样的选项来选择不延续。

但是,如果在异步任务在工作时收到取消请求,异步操作也可以选择不立刻结束,而是等当前正在执行的工作完成后再结束,并返回 RanToCompletion 状态的任务;也可以终止当前工作并强制结束,根据实际业务情况和是否生产异常结果返回 Canceled 或 Faulted 状态。

对于不能被取消的业务方法,不要提供接受取消令牌的重载,这有助于向调用者表明目标方法是否可以取消。

3 进度报告

几乎所有异步操作都可以提供进度通知,这些通知通常用于用异步操作的进度信息更新用户界面。

在 TAP 中,进度是通过 IProgress<T> 接口来处理的,该接口作为一个参数传递给异步方法。下面是一个典型的的使用示例:


static void Main(string[] args)
{
    var progress = new Progress<int>(n =>
    {
        Console.WriteLine($"当前进度:{n}%");
    });

    var task = DoWork(progress);

    Console.ReadKey();
}

public static async Task DoWork(IProgress<int> progress)
{
    for (int i = 1; i <= 100; i++)
    {
        await Task.Delay(100);
        if (i % 10 == 0)
        {
            progress?.Report(i);
        };
    }
}

输出如下结果:

当前进度:10%
当前进度:20%
当前进度:30%
当前进度:40%
当前进度:50%
当前进度:60%
当前进度:70%
当前进度:80%
当前进度:90%
当前进度:100%

IProgress<T> 接口支持不同的进度实现,这是由消费代码决定的。例如,消费代码可能只关心最新的进度更新,或者希望缓冲所有更新,或者希望为每个更新调用一个操作,等等。所有这些选项都可以通过使用该接口来实现,并根据特定消费者的需求进行定制。例如,如果本文前面的 ReadAsync 方法能够以当前读取的字节数的形式报告进度,那么进度回调可以是一个 IProgress<long> 接口。


public Task ReadAsync(
    byte[] buffer, int offset, int count,
    IProgress<long> progress)

再如 FindFilesAsync 方法返回符合特定搜索模式的所有文件列表,进度回调可以提供工作完成的百分比和当前部分结果集,它可以用一个元组来提供这个信息。


public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
    string pattern,
    IProgress<Tuple<double, ReadOnlyCollection<List<FileInfo>>>> progress)

或使用 api 特有的数据类型:


public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
    string pattern,
    IProgress<FindFilesProgressInfo> progress)

如果 TAP 的实现提供了接受 IProgress<T> 参数的重载,它们必须允许参数为空,在这种情况下,不会报告进度。IProgress<T> 实例可以作为独立的对象,允许调用者决定如何以及在哪里处理这些进度信息。

4 Task.Yield 让步

我们先来看一段 Task.Yield() 的代码:


Task.Run(async () =>
{
    for(int i=0; i<10; i++)
    {
        await Task.Yield();
        ...
    }
});

这里的 Task.Yield() 其实什么也没干,它返回的是一个空任务。那 await 一个什么也没做的空任务有什么用呢?

我们知道,对计算机来说,任务调度是根据一定的优先策略来安排线程去执行的。如果任务太多,线程不够用,任务就会进入排队状态。而 Yield 的作用就是让出等待的位置,让后面排除的任务先行。它字面上的意思就是让步,当任务做出让步时,其它任务就可以尽快被分配线程去执行。举个现实生活中的例子,就像你在排队办理业务时,好不容易到你了,但你的事情并不急,自愿让出位置,让其他人先办理,自己假装临时有事到外面溜一圈什么事也没干又回来重新排队。默默地做了一次大善人。

Task.Yield() 方法就是在异步方法中引入一个让步点。当代码执行到让步点时,就会让出控制权,去线程池外面兜一圈什么事也没干再回来重新排队。

5 定制异步任务后续操作

我们可以对异步任务执行完成的后续操作进行定制。常见的两个方法是 ConfigureAwait 和 ContinueWith。

ConfigureAwait

我们先来看一段 windows FORM 中的代码:


private void button1_Click(object sender, EventArgs e)
{
    var content = CurlAsync().Result;
    ...
}

private async Task<string> CurlAsync()
{
    using (var client = new HttpClient())
    {
        returnawait client.GetStringAsync("http://geekGISt.com");
    }
}

想必大家都知道 CurlAsync().Result 这句代码在 Windows Form 程序中会造成死。原因是 UI 主线程执行到这句代码时,就开始等待异步任务的结果,处于阻塞状态。而异步任务执行完后回来准备找 UI 线程继续执行后面的代码时,却发现 UI 线程一直处于“忙碌”的状态,没空搭理回来的异步任务。这就造成了你等我,我又在等你的尴尬局面。

当然,这种死锁的情况只会在 winform 和早期的 asp.net WEBForm 中才会发生,在 Console 和 Web API 应用中不会生产死锁。

解决办法很简单,作为异步方法调用者,我们只需改用 await 即可:


private async void button1_Click(object sender, EventArgs e)
{
    var content = await CurlAsync();
    ...
}

在异步方法内部,我们也可以调用任务的 ConfigureAwait(false) 方法来解决这个问题。如:


private async Task<string> CurlAsync()
{
    using (var client = new HttpClient())
    {
        returnawait client
            .GetStringAsync("http://geekgist.com")
            .ConfigureAwait(false);
    }
}

虽然两种方法都可行,但如果作为异步方法提供者,比如封装一个通用库时,考虑到难免会有新手开发者会使用 CurlAsync().Result,为了提高通用库的容错性,我们就可能需要使用 ConfigureAwait 来做兼容。

ConfigureAwait(false) 的作用是告诉主线程,我要去远行了,你去做其它事情吧,不用等我。只要先确保一方不在一直等另一方,就能避免互相等待而造成死锁的情况。

ContinueWith

ContinueWith 方法很容易理解,就是字面上的意思。作用是在异步任务执行完成后,安排后续要执行的工作。示例代码:


private void Button1_Click(object sender, EventArgs e)
{
    var backgroundScheduler = TaskScheduler.Default;
    var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory
        .StartNew(_ => DoBackgroundComputation(), backgroundScheduler)
        .ContinueWith(_ => UpdateUI(), uiScheduler)
        .ContinueWith(_ => DoAnotherBackgroundComputation(), backgroundScheduler)
        .ContinueWith(_ => UpdateUIAgain(), uiScheduler);
}

如上,可以一直链式的写下去,任务会按照顺序执行,一个执行完再继续执行下一个。若其中一个任务返回的状态是 Canceled 时,后续的任务也将被取消。这个方法有好些个重载,在实际用到的时候再查看文档即可。

6 总结

本文内容都是相对比较基础的 TAP 异步操作知识点。C# 的 TAP 很强大,提供的 API 也很多,远不止本文讲的这些,都是围绕 Task 转的。关键是要理解好基础操作,才能灵活使用更高级的功能。希望本文对你有所帮助。

以上就是c# 几个常见的TAP异步操作的详细内容,更多关于c# TAP异步操作的资料请关注编程网其它相关文章!

--结束END--

本文标题: c# 几个常见的TAP异步操作

本文链接: https://lsjlt.com/news/123258.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • c# 几个常见的TAP异步操作
    目录1 任务状态手动控制任务启动确保任务已激活2 任务取消3 进度报告4 Task.Yield 让步5 定制异步任务后续操作ConfigureAwaitContinueWith6 总...
    99+
    2024-04-02
  • React useEffect异步操作常见问题小结
    目录三个常见的问题:一、react hooks发异步请求二、如何在组件加载的时候发起异步任务三、如果在响应回来之前组件被销毁了会怎样?四、如何在组件交互时发起异步任务为什么两种写法会...
    99+
    2024-04-02
  • java常见的几种异常
    异常,根据字面理解,有意外之意。把它置于代码层面来理解,即阻止了当前方法或作用域继续执行。在Java中,异常被当做对象来处理,其基类是Throwable。java常见的几种异常:1、空指针异常类:NullPointerException调用...
    99+
    2022-04-17
    java基础 java 异常
  • C#操作Word表格常见的操作
    本篇内容主要讲解“C#操作Word表格常见的操作”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#操作Word表格常见的操作”吧!几种常见C#操作Word表格操作有哪些呢?让我们来看看具体的实例...
    99+
    2023-06-17
  • PHP异常处理:处理异步操作中的异常
    处理异步操作中的 php 异常需要:协程中,使用 try-catch-finally 语法捕获异常。promise 中,使用 then() 和 catch() 方法处理异常。实战案例:使...
    99+
    2024-05-14
    异常处理 异步处理 swoole
  • C#多线程TPL常见操作误区与异常处理
    一、一定要async到底 一定要让async的传染性(调用异步方法要用await,用了await的方法就要声明为async,调用我这个async方法的地方必须要await.....)...
    99+
    2024-04-02
  • 【C#基础】C# 异常处理操作
    序号系列文章6【C#基础】C# 常用语句讲解7【C#基础】C# 常用数据结构8【C#基础】C# 面向对象编程 文章目录 前言1,异常的概念2,处理异常3,自定义异常4,编译器异常结语 前言 🌷大家好,我是wr...
    99+
    2023-08-18
    c# 开发语言 java
  • C#如何实现异步操作
    这篇文章给大家分享的是有关C#如何实现异步操作的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。.NET Framework 为异步操作提供了两种设计模式:使用 IAsyncResult 对象的异步操作与使用事件的异...
    99+
    2023-06-18
  • C#线程操作常见的操作方法有哪些
    这篇文章主要讲解了“C#线程操作常见的操作方法有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#线程操作常见的操作方法有哪些”吧!C#线程操作常见的操作方法是什么呢?C#线程操作方法的...
    99+
    2023-06-17
  • Python工作中出现的几个常见问题
    这篇文章将为大家详细讲解有关Python工作中出现的几个常见问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。python的五大特点是什么python的五大特点:1.简单易学,开发程序时,专注的是解决问题...
    99+
    2023-06-14
  • JAVA常见的运行异常有哪几种
    在Java中,常见的运行时异常有以下几种: NullPointerException(空指针异常):当程序试图访问一个空对象的属性...
    99+
    2024-02-29
    JAVA
  • C++中怎么实现异步操作
    C++中怎么实现异步操作,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。实现(代码)#include <iostream> #include&nb...
    99+
    2023-06-17
  • C#中怎么实现异步操作
    C#中怎么实现异步操作,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。//首先准备好,要进行异步的方法(能异步的,***不多线程)  private ...
    99+
    2023-06-17
  • C#异步使用需要注意的几个问题
    目录一、异步模型的基本概述二、C#异步使用需要注意的几个问题三、CancellationToken 结构体1.手动取消2.利用方法取消,抛出异常3. 传参数取消4.手动触发事件取消任...
    99+
    2024-04-02
  • C语言常见的文件操作函数
    目录一、文件的打开和关闭1、文件指针2、文件打开和关闭二、文件的顺序读写 1、fgetc()和fputc()函数2、fgets()和fputs()函数 3、fsc...
    99+
    2024-04-02
  • 总结20个常见的JavaScript数组操作
    JavaScript中的Array对象与其他编程语言中的数组一样,是一组数据的集合。在JavaScript中,数组里面的数据可以是不同类型的,并具有用于执行数组常见操作的方法。声明数组有三种不同的声明方式1. 常规方式const hobby...
    99+
    2023-05-14
    前端
  • 20个常见的JavaScript数组操作总结
    目录声明数组1. 常规方式2. 简洁方式3. 字面Array 对象方法1. forEach2. map3. concat4. push5. unshift6. pop7. shift...
    99+
    2024-04-02
  • c++不能重载的操作符有几个
    c++ 中不能重载的唯一运算符是范围运算符(::)。 C++ 中不能重载的操作符数量 C++ 中不能重载的运算符只有 一个,那就是 范围运算符::。以上就是c++++不能重载的操作符有...
    99+
    2024-04-22
    c++
  • C#中怎么实现长异步操作
    本篇文章为大家展示了C#中怎么实现长异步操作,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。程序中执行按钮的Click 处理如下:private void _btnRun_Clic...
    99+
    2023-06-18
  • JavaScript中几个最常见的错误
    这篇文章主要介绍“JavaScript中几个最常见的错误”,在日常操作中,相信很多人在JavaScript中几个最常见的错误问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Ja...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作