异步

异步

异步编程的核心概念

async 关键字

  • 用于标记一个方法为异步方法。
  • 异步方法的返回类型通常是 Task、Task<T> 或 ValueTask。
    Task:表示一个没有返回值的异步操作。
    Task<T>:表示一个返回类型为 T 的异步操作。
    ValueTask:轻量版的 Task,适用于高性能场景。

await 关键字

  • 用于暂停异步方法的执行,直到等待的任务完成。
  • await 只能用于 async 方法中。
  • 它不会阻塞线程,而是将控制权交回给调用方,直到任务完成后再恢复执行。

异步编程的基本语法

定义异步方法

public async Task GetDataAsync()
{
    // 异步操作
    await Task.Delay(1000); // 模拟耗时操作
}

调用异步方法

public async Task CallAsyncMethod()
{
    await GetDataAsync(); // 等待异步方法完成
    Console.WriteLine("异步方法已调用.");
}

并行

var call1 = Call1();
var call2 = Call2();
await Task.WhenAll(call1, call2);

async Task MethodAsync(int seconds) => await Task.Delay(seconds);

async Task Call1()
{
    "start call1".Dump();
    await MethodAsync(2000);
    "done call1".Dump();
}
async Task Call2()
{   
    "start call2".Dump();
    await MethodAsync(2000);
    "done call2".Dump();
}

概念

  • 异步不意味着多线程
  • 异步默认借助线程池
  • 异步要求不阻塞,多线程经常阻塞(Thread.Sleep())
  • 多线程适合CPU密集型操作,创建与销毁线程开销大,提供底层控制,不易于传参和返回,代码繁琐
  • 异步适合I/O密集型操作,适合短暂的小任务,提高系统响应能力

异步任务(Task)

包含了异步任务的各种状态的引用类型:(另有ValueTask值类型版本)

var task = new Task<string>( () =>
{
    Thread.Sleep(1500);
    return "done";
});

task.Status.Dump();
task.Start();
task.Status.Dump();
Thread.Sleep(1000);
task.Status.Dump();
Thread.Sleep(501);
task.Status.Dump();

task.Result.Dump("Result");

对异步任务的抽象:开启异步任务后,当前线程不会阻塞,而是可以做其他事情,异步任务会借助线程池在其他线程上运行,获取结果后回到之前的状态。

任务的结果:

  • 返回值为Task的方法表示异步任务没有返回值
  • 返回值为Task表示有类型为T的返回值

异步方法(async Task)

  • 方法标记为async后,可以在方法中使用await关键字
  • await关键字会等待异步任务结束,获得结果
  • async+await会把方法包装成状态机,await类似于检查点
  • async void缺少记录状态的Task对象,无法聚合异常,需要谨慎处理异常,几乎只用于对于事件的注册(Action)
  • 传染性:移除async,处处async;几乎所有自带方法都提供了异步的版本(如果没有,可以用Task.Run包装)

重要思想:不阻塞!

  • await会释放当前线程,使其可以执行其他工作,而不必阻塞线程直到异步操作完成
  • 不要在异步方法里面用任何方式阻塞当前线程(Task.Wait() Task.Result() 阻塞线程,容易导致死锁)
  • Thread.Sleep -> Task.Delay 前者阻塞,后者异步会释放当前线程

同步上下文

一种管理和协调线程的机制,允许开发者将代码的执行 切换到特定的线程

WinForms和WPF默认拥有同步上下文(UI线程),控制台默认没有

Task.ConfigureAwait( false ):配置任务通过await方法结束后,若为true则回到原线程 一般只有UI线程会使用

TaskScheduler

一发即忘

调用一个异步方法,但不使用await或阻塞去等待结束,无法观察任务的状态

int HeavyJob()
{
    Console.WriteLine($"HeavyJob 开始: {Environment.CurrentManagedThreadId}");
    Thread.Sleep(3000);
    Console.WriteLine($"HeavyJob 结束");
    return 43;
}

Console.WriteLine($"主线程开始: {Environment.CurrentManagedThreadId}");

var task = Task.Run(() => HeavyJob());

// 在等待期间做其他事情
for(int i = 1; i <= 8; i++)
{
    Console.WriteLine($"等待期间做其他事");
    Thread.Sleep(500);
}

int result = await task;  // 此时可能已经完成,或者继续等待
Console.WriteLine($"结果: {result}");

Task.Factory.StartNew() 相比Task.Run提供更多功能

posted @ 2026-04-01 14:39  hélium  阅读(18)  评论(0)    收藏  举报