欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

[C#图解教程]笔记 - 20.异步编程

最编程 2024-03-14 14:46:00
...

什么是异步

启动程序时,系统会在内存中创建一个新的进程。进程是构成运行程序的资源的集合。
在进程内部,系统创建了一个称为线程的内核(kernel)对象。

关于线程:

  • 默认情况下,一个进程只包含一个线程,从程序的开始一直到执行到结束。
  • 线程可以派生其他线程,一个进程可能包含不同状态的多个线程。
  • 如果一个进程有多个线程,它们将共享进程的资源。
  • 系统为处理器执行所调度的单元是线程,不是进程。

async/await

异步的方法在完成其所有工作之前就返回到调用方法。

  • 调用方法(calling method):该方法调用异步方法,然后再异步方法执行其任务的时候继续执行。
  • 异步(async)方法:该方法异步执行其工作,然后立即返回到调用方法。
  • await 表达式:用于异步方法内部,指明需要异步执行的任务。一个异步方法可以包含任意多个 await 表达式,如果一个都不包含的话编译器会发出警告。

异步方法

  • 异步方法的头方法中必须包含 async 关键字,且必须位于返回类型之前。
  • async 关键字是一个上下文关键字,除了作为方法修饰符之外,还可以用作标识符。
  • 异步方法的返回类型:
    • Task:不需要返回值,但需要检查异步方法的状态
    • Task<T>:调用方法通过读取 Task 的Result 属性来获取这个 T 类型的值。
    • ValueTask<T>:这是一个值类型对象,他与 Task<T> 类似,但用于任务结果可能已经可用的情况。因为它是值类型,所以它可以放在栈上,而无须像 Task<T> 对象那样再堆上分配空间。某种情况下可以提高性能。
    • void:调用方法仅想执行异步,而不需要与他做任何进一步的交互时。
    • 任何具有可访问的 GetAwaiter 方法的类型。

await 表达式

​ await 表达式指定了一个异步执行的任务,由 await 关键字和一个空闲对象(称为任务,可能是 Task 类型,也可能不是)组成。默认情况下,这个任务在当前线程上异步执行。
一个空闲对象即是 awaitable 类型的实例。awaitable 类型包含了 GetAwaiter 方法,该方法没有参数,返回一个 awaiter 类型的对象。
awaiter 类型包含如下成员:

  • bool IsCompleted { get; }
  • void OnCompleted(Action)
    同时还包含以下成员之一:
  • void GetResult();
  • T GetResult();T 为任意类型
    实际上,不需要构建自己的 awaitable,而是使用 Task 类或 ValueTask 类,它们是 awaitable 类型。通过 Task.Run 方法来创建 Task,其签名如下。其中 Func<TReturn> 是一个预定义委托,不包含任何参数,返回类型为 TReturn。

取消一个异步操作

System.Threading.Tasks 命名空间中有两个类被设计为取消异步操作,分别为 CancellationToken 和 CancellationTokenSource。

  • CancellationToken 包含一个任务是否应被取消的信息 IsCancellationRequested。
  • 拥有 CancellationToken 对象的任务需要定期检查其令牌状态,如果 IsCancellationRequested 属性为 true,任务需停止其操作并返回。
  • CancellationToken 不可逆,且只能使用一次。即,一旦 IsCancellationRequested 被设置为 true,就不能更改。
  • CancellationTokenSource 对象创建可分配给不同任务的 CancellationToken 对象,任何持有 CancellationTokenSource 的对象都可以调用其 Cancel 方法,这将使 IsCancellationRequested 被设置为 true。

异常处理和 await 表达式

await 表达式可以像其他表达式一样放在 try 语句内,try … catch … finally 结构将按你期望的那样工作。
C#6.0 后也可以在 catch 和 finally 块中使用 await 表达式。在异常不需要终止应用程序时,可以使用 await 来记录日志或运行其他时间较长的任务。如果新的异步任务也产生了一场,则任何原有的异常信息都将丢失。

在调用方法中同步地等待任务

使用 Wait 方法可以等待 Task 对象完成。
Wait 方法用于单一 Task 对象。
可以使用 Task 类中的静态方法等待一组 Task 对象完成。

  • WaitAll :等待一组 Task 对象全部完成。
  • WaitAny:等待一组中至少一个 Task 对象完成。
    WaitAll 和 WaitAny 分别包含 4 个重载,除了完成任务之外,还允许以不同的方式继续执行,如设置超时时间或使用 CancellationToken 来强制执行处理的后续部分。

image.png

在异步方法中异步地等待任务

使用 Task.WhenAll 和 Task.WhenAny 异步等待多个 Task。这两个方法称为组合子(combinator)。

Task.Delay 方法

Task.Delay 方法创建一个 Task 对象,该对象将暂停其在线程中的处理,并在一定时间之后完成。

  • Thread.Sleep:阻塞线程。
  • Task.Delay:不阻塞线程,线程可以继续处理其他工作。

例:

public class Simple  
{  
    private Stopwatch sw = new Stopwatch();  
  
    public void DoRun()  
    {  
        Console.WriteLine("Caller: Before call");  
        ShowDelayAsync();  
        Console.WriteLine("Caller: After call");  
    }  
  
    private async void ShowDelayAsync()  
    {  
        sw.Start();  
        Console.WriteLine("Before Delay:"+sw.ElapsedMilliseconds);  
        await Task.Delay(1000);  
        Console.WriteLine("After Delay:"+sw.ElapsedMilliseconds);  
    }  
}
public class Program  
{  
    public static Main(string[] args)  
    {  
        Simple ds = new Simple();  
        ds.DoRun();  
        Console.ReadLine();  
    }
}

执行结果:

Caller: Before call
Before Delay:0
Caller: After call
After Delay:1003

image.png

Task.Yield

Task.Yield 方法创建一个立即返回的 awaitable。 等待一个 Yield 可以让异步方法在执行后续部分的同时返回到调用方法。可以将其理解成离开当前的消息队列,回到队列末尾,让处理器有时间处理其他任务。

使用异步 Lambda 表达式

除了异步方法,还可以使用异步匿名方法和异步 Lambda 表达式。
例:

startWorkButton.Click += async (sender, e) => { ... };

BackgroundWorker 类

可以使用 BackgroundWorker 类创建后台线程。
image.png

并行循环

任务并行库(Task Parallel Library)
这里只介绍两个:

  • Parallel.For 循环
  • Parallel.ForEach 循环

其他异步编程

当委托调用时,他的调用列表只有一个方法,它就可以异步执行这个方法。

  • BeginInvoke:
    • 参数列表:引用方法需要的参数,callback 参数,state 参数
    • BeginInvoke 从线程池中获取一个线程并且引用方法在新的线程中开始运行,返回给调用线程一个实现 IAsyncResult 的接口的对象的引用。这个接口引用包含了在线程池中运行的异步方法的当前状态。然后原始线程可以继续执行。
  • EndInvoke:
    • 参数列表:IAsyncResult 对象的引用
    • EndInvoke 方法用来获取由异步方法调用返回的值,并且释放线程使用的资源。
    • EndInvoke 提供了异步方法调用的所有输出,包括 ref 和 out 参数,如果委托的引用方法有 ref 或 out 参数,则它们必须包含在 EndInvoke 的参数列表中,并且在 IAsyncResult 对象引用之前。如:long result = del.EndInvoke(out someInt, iar);

异步方法调用的三种标准模式

image.png

  • 等待直到完成模式中,在发起了异步方法以及做了一些其他处理之后,原始线程就中断并且等异步方法完成之后再继续。
  • 轮询模式中,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其他的事情。
  • 回调模式中,原始线程一直执行,无须等待或检查发起的线程是否完成。