You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中Async/Await不同调用方式的误区与疑问

C#异步编程常见问题解答

示例异步方法代码

async Task Do()
{
  Console.WriteLine("Do Start 1");
  Console.WriteLine("Do Start 2");
  await Task.Delay(3500);
  Console.WriteLine("Do 1");
  Console.WriteLine("Do 2");
}

async Task Foo()
{
  Console.WriteLine("Foo Start 1");
  Console.WriteLine("Foo Start 2");
  await Task.Delay(1500);
  Console.WriteLine("Foo 1");
  Console.WriteLine("Foo 2");
}

async Task Bar()
{
  Console.WriteLine("Bar Start 1");
  Console.WriteLine("Bar Start 2");
  await Task.Delay(2500);
  Console.WriteLine("Bar 1");
  Console.WriteLine("Bar 2");
}

调用变体I与II

代码

// I变体
Do();
Foo();
Bar();

// II变体
var one = Do();
var two = Foo();
var three = Bar();

// 主线程逻辑
for (int i = 1; i <= 3; i++)
{
  await Task.Delay(900);
  Console.WriteLine($"Main({i})");
}

Console.ReadLine();

输出

Do Start 1
Do Start 2    
Foo Start 1
Foo Start 2    
Bar Start 1
Bar Start 2    
Main(1)
Foo 1
Foo 2    
Main(2)
Bar 1
Bar 2    
Main(3)
Do 1
Do 2

技术问询1:为何I、II变体执行效果相同,但Visual Studio仅对I变体发出未await警告?为何不能不await调用async函数?

解答:

  1. 警告差异原因

    • I变体直接调用Do();,未接收返回的Task对象,编译器默认判定你大概率遗漏了异步操作的后续处理(如等待完成、捕获异常),因此触发警告。
    • II变体将返回的Task赋值给变量,编译器认为你有意保留异步操作的引用,可能后续会处理(比如之后await、监控状态),所以不触发警告。但两种写法在这段代码里的执行逻辑完全一致——都是启动三个异步任务后直接执行主线程逻辑,未等待任务完成。
  2. 不await调用async函数的问题

    • 异常丢失:异步方法抛出的异常会封装在返回的Task中,若不await也不访问该Task,异常会被静默丢弃,排查难度极大。
    • 执行顺序不可控:无法确定异步任务的完成时机,也无法在任务完成后执行依赖逻辑。
    • 资源泄漏风险:若异步任务持有未释放的资源,未等待完成可能导致资源无法及时回收。
    • 调试困难:未await的异步任务执行状态无法被监控,出现问题时难以追踪流程。

调用变体III

代码

var one = Do();
var two = Foo();
var three = Bar();

// a 写法
await one;
await two;
await three;

// b 等效写法
// await Task.WhenAll(one, two, three);

// 主线程逻辑
// for (int i = 1; i <= 3; i++)
// {
//   await Task.Delay(900);
//   Console.WriteLine($"Main({i})");
// }

输出

Do Start 1
Do Start 2    
Foo Start 1
Foo Start 2    
Bar Start 1
Bar Start 2    
Foo 1
Foo 2    
Bar 1
Bar 2    
Do 1
Do 2    
Main(1)
Main(2)
Main(3)

技术问询2:III变体中主线程会等待任务完成,在Program.cs的Main线程中使用该变体相比I、II有何优势?

解答:

  1. 异常可捕获:通过awaitTask,异步方法抛出的异常会正常抛出,你可以用try/catch捕获处理,避免异常静默丢失。
  2. 确保任务完成:主线程会等待三个异步任务全部完成后,再执行后续逻辑,适合必须依赖异步任务结果或状态的场景(如任务完成后输出汇总数据、释放资源)。
  3. 执行流程可控:明确知道异步任务的完成时机,不会出现主线程逻辑与异步任务逻辑混乱交织的情况,代码可读性和可维护性更高。
  4. 避免后台任务被终止:控制台程序中Main线程结束后整个进程会退出,I、II变体中若主线程先结束,未完成的异步任务可能被强制终止;III变体等待任务完成后再执行后续逻辑,能保证异步任务执行完毕。

调用变体IV

代码

await Do();
await Foo();
await Bar();

// 主线程逻辑
// for (int i = 1; i <= 3; i++)
// {
//   await Task.Delay(900);
//   Console.WriteLine($"Main({i})");
// }

输出

Do Start 1
Do Start 2
Do 1
Do 2
Foo Start 1
Foo Start 2
Foo 1
Foo 2
Bar Start 1
Bar Start 2
Bar 1
Bar 2
Main(1)
Main(2)
Main(3)

技术问询3:IV变体是同步执行异步方法,适用场景有哪些?

解答:

IV变体本质是串行执行异步任务,上一个任务完全完成后才启动下一个,适用于以下场景:

  • 任务间有依赖关系:比如Foo的执行需要依赖Do的结果,Bar需要Foo的输出,必须按顺序执行。
  • 资源独占场景:多个异步任务需要访问同一独占资源(如单个文件、硬件设备),串行执行可避免并发访问冲突,无需额外加锁。
  • 严格的执行顺序要求:业务逻辑要求任务必须按指定顺序完成,比如日志需按操作顺序写入,或流程步骤必须依次执行。
  • 限制并发量:当系统资源有限(如数据库连接数不足),串行执行可避免同时发起过多异步请求导致资源耗尽。

内容的提问来源于Stack Exchange,提问作者DevMis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:45:01