找回密码
 立即注册
快捷导航

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

[复制链接]
admin 2024-12-14 07:16:39 | 显示全部楼层

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

C# Async 和 Await 到底在做什么?6870 作者:admin 帖子ID:1168

在这个例子中:

  • 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结束");
}

C# Async 和 Await 到底在做什么?5710 作者:admin 帖子ID:1168

执行流程如下:

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

C# Async 和 Await 到底在做什么?2086 作者:admin 帖子ID:1168

9.2 带超时的异步操作

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

static async Task Main(string[] args)
{
    await FetchDataWithTimeoutAsync("http://www.google.com",new TimeSpan(0, 0, 3));
    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#中的异步编程,使得编写高效、响应式的应用程序变得更加容易。通过将复杂的异步操作转换为看似同步的代码,它们提高了代码的可读性和可维护性。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

温馨提示

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

Archiver|手机版|小黑屋|DLSite

GMT+8, 2025-1-18 13:00

Powered by Discuz! X3.5 and PHP8

快速回复 返回顶部 返回列表