LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C# Async 和 Await 到底在做什么?

admin
2024年12月7日 8:8 本文热度 225

1. 引言

在C#中,asyncawait关键字是用于实现异步编程的强大工具。它们的引入极大地简化了异步代码的编写,使得开发人员能够更容易地创建响应式和高性能的应用程序。但是,要真正理解它们的工作原理,我们需要深入探讨它们在底层到底在做什么。

2. 异步编程的基本概念

在深入asyncawait之前,我们需要理解一些基本概念:

  • 同步执行: 代码按顺序执行,每个操作完成后才会进行下一个。

  • 异步执行: 允许长时间运行的操作在后台进行,而不阻塞主线程。

  • 任务(Task): 表示一个异步操作。

  • 线程: 程序执行的最小单位。

3. Async 和 Await 的基本用法

让我们从一个简单的例子开始:

static async Task Main(string[] args)
{
    var context = await GetWebContentAsync("http://www.baidu.com");
    Console.WriteLine(context);
}

public static async Task<string> GetWebContentAsync(string url)
{
    using (var client = new HttpClient())
    {
        string content = await client.GetStringAsync(url);
        return content;
    }
}

在这个例子中:

  • async关键字标记方法为异步方法。

  • 方法返回Task,表示一个最终会产生string的异步操作。

  • await用于等待GetStringAsync方法完成,而不阻塞线程。

4. Async 方法的转换过程

当你使用async关键字标记一个方法时,编译器会将其转换为一个状态机。这个过程大致如下:

  1. 创建一个实现了IAsyncStateMachine接口的结构体。

  2. 将方法体转换为状态机的MoveNext方法。

  3. 每个await表达式都成为一个可能的暂停点,对应状态机中的一个状态。

async方法如何被分解为多个步骤,每个await表达式对应一个状态。

5. Await 的工作原理

await关键字的主要作用是:

  1. 检查awaited任务是否已完成。

  2. 如果已完成,继续执行后续代码。

  3. 如果未完成,注册一个回调并返回控制权给调用者。

让我们通过一个例子来详细说明:

public async Task DoWorkAsync()
{
    Console.WriteLine("开始工作");
    await Task.Delay(1000); // 模拟耗时操作
    Console.WriteLine("工作完成");
}

当执行到await Task.Delay(1000)时:

  1. 检查Task.Delay(1000)是否已完成。

  2. 如果未完成:

    • 创建一个continuation(后续操作),包含`await`之后的代码。

    • 将这个continuation注册到Task上。

    • 返回控制权给调用者。

  3. Task.Delay(1000)完成时:

    • 触发注册的continuation。

    • 恢复执行`await`之后的代码。

6. 异步方法的执行流程

让我们通过一个更复杂的例子来理解异步方法的执行流程:

static async Task Main(string[] args)
{
    await MainMethodAsync();
    Console.ReadKey();
}

public static async Task MainMethodAsync()
{
    Console.WriteLine("1. 开始主方法");
    await Method1Async();
    Console.WriteLine("4. 主方法结束");
}

public static async Task Method1Async()
{
    Console.WriteLine("2. 开始方法1");
    await Task.Delay(1000);
    Console.WriteLine("3. 方法1结束");
}

执行流程如下:

  1. MainMethodAsync开始执行,打印"1. 开始主方法"。

  2. 遇到await Method1Async(),进入Method1Async

  3. Method1Async打印"2. 开始方法1"。

  4. 遇到await Task.Delay(1000),注册continuation并返回。

  5. 控制权回到MainMethodAsync,但因为Method1Async未完成,所以MainMethodAsync也返回。

  6. 1秒后,Task.Delay完成,触发continuation。

  7. Method1Async继续执行,打印"3. 方法1结束"。

  8. Method1Async完成,触发MainMethodAsync的continuation。

  9. MainMethodAsync继续执行,打印"4. 主方法结束"。

7. 异常处理

async/await模式下的异常处理非常直观。你可以使用常规的try/catch块,异步方法中抛出的异常会被封装在返回的Task中,并在await时重新抛出。

8. 避免常见陷阱

使用async/await时,有一些常见的陷阱需要注意:

8.1 死锁

考虑以下代码:

public async Task DeadlockDemoAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);
}

public void CallAsyncMethod()
{
    DeadlockDemoAsync().Wait(); // 可能导致死锁
}
  • 当在UI线程(或任何有同步上下文的线程)中调用CallAsyncMethod()时,会发生死锁。

  • Wait()方法会阻塞当前线程,等待异步操作完成。

  • 当异步操作完成时,它默认会尝试在原始的同步上下文(通常是UI线程)上继续执行。

  • 但是原始线程已经被Wait()阻塞了,导致死锁。

8.2 忘记await

public async Task ForgetAwaitDemoAsync()
{
    DoSomethingAsync(); // 忘记await
    Console.WriteLine("完成"); // 这行可能在异步操作完成之前执行
}

始终记得在异步方法调用前使用await

8.3 过度使用async void

除了事件处理程序外,应避免使用async void方法,因为它们的异常难以捕获和处理。

public async void BadAsyncVoidMethod()
{
    await Task.Delay(1000);
    throw new Exception("这个异常很难被捕获");
}

9. 高级模式

9.1 并行执行多个任务

使用Task.WhenAll可以并行执行多个异步任务:

public async Task ParallelExecutionDemo()
{
    var task1 = DoWorkAsync(1);
    var task2 = DoWorkAsync(2);
    var task3 = DoWorkAsync(3);

    await Task.WhenAll(task1, task2, task3);
    Console.WriteLine("所有任务完成");
}

public async Task DoWorkAsync(int id)
{
    await Task.Delay(1000);
    Console.WriteLine($"任务 {id} 完成");
}

9.2 带超时的异步操作

使用Task.WhenAnyTask.Delay可以实现带超时的异步操作:

static async Task Main(string[] args)
{
    await FetchDataWithTimeoutAsync("http://www.google.com",new TimeSpan(003));
    Console.ReadKey();
}

static async Task<string> FetchDataWithTimeoutAsync(string url, TimeSpan timeout)
{
    using (var client = new HttpClient())
    {
        var dataTask = client.GetStringAsync(url);
        var timeoutTask = Task.Delay(timeout);

        var completedTask = await Task.WhenAny(dataTask, timeoutTask);
        if (completedTask == timeoutTask)
        {
            throw new TimeoutException("操作超时");
        }

        return await dataTask;
    }
}

10. 结论

asyncawait极大地简化了C#中的异步编程,使得编写高效、响应式的应用程序变得更加容易。通过将复杂的异步操作转换为看似同步的代码,它们提高了代码的可读性和可维护性。


该文章在 2024/12/9 18:38:49 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved