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

使用TaskCompletionSource和AutoResetEvent同步时出现奇怪死锁的原因

异步代码中AutoResetEvent等待超时的原因分析

运行以下C#程序时,会随机出现两种不同的执行结果:结果1频繁出现等待超时(并非真正死锁,最终仍能完成),结果2则正常执行。以下是对该现象的具体解释。

程序代码

class Program
{
    static async Task Main()
    {
        TaskCompletionSource<bool> tcs = new();
        AutoResetEvent evtFinished = new AutoResetEvent(false);
        Func<Task> func = async Task () => 
            { await Task.Delay(10); tcs.SetResult(true); evtFinished.Set(); };
        var t = func.Invoke();
        //await func.Invoke();
        await tcs.Task;
        if (evtFinished.WaitOne(1000))
            Console.WriteLine("First Wait OK");
        else
        {
            Console.WriteLine("First Wait Timeout");
            for (int i = 0; i < 10 && !evtFinished.WaitOne(100); i++)
                Console.WriteLine($" {i+1} Wait Timeout"); 
            await t;
            evtFinished.WaitOne();
            Console.WriteLine("Wait OK");
        }
    }
}

执行结果

结果1(频繁出现)

<<<<<Result 1: >>>>>>>
First Wait Timeout
 1 Wait Timeout
 2 Wait Timeout
 3 Wait Timeout
 4 Wait Timeout
 5 Wait Timeout
 6 Wait Timeout
 7 Wait Timeout
 8 Wait Timeout
 9 Wait Timeout
 10 Wait Timeout
Wait OK

结果2

<<<<<Result2 >>>>>>
First Wait OK

原因分析

核心原因:异步延续的调度时序与阻塞调用的冲突

这不是真正的死锁,而是线程调度的时序差异导致的等待超时,关键在于func方法中await Task.Delay(10)完成后,后续代码的执行时机和主线程的阻塞操作之间的交互:

  1. 调用func.Invoke()时,方法执行到await Task.Delay(10)会立即返回未完成的Task,主线程继续执行await tcs.Task。
  2. Task.Delay(10)完成后,func的剩余代码(tcs.SetResult(true); evtFinished.Set();)会被调度到线程池线程执行(控制台应用无同步上下文,await默认回退到线程池)。

结果2(正常执行)的场景

func的后续代码中,tcs.SetResult(true)和evtFinished.Set()几乎同时执行,主线程在await tcs.Task恢复执行时,evtFinished已经被设置为有信号状态,因此evtFinished.WaitOne(1000)能立即获取信号,输出"First Wait OK"。

结果1(等待超时)的场景

func中tcs.SetResult(true)执行后,主线程的await tcs.Task立即恢复执行,随即调用evtFinished.WaitOne(1000)阻塞当前线程。但此时evtFinished.Set()还未执行——由于线程调度的延迟,func的剩余代码可能还在等待线程池线程的调度,导致evtFinished仍处于无信号状态,主线程的等待因此超时。

当主线程执行到await t;时,会异步等待func的Task完成,此时func的剩余代码(evtFinished.Set())会被执行完毕,之后evtFinished.WaitOne()就能获取信号,最终输出"Wait OK"。

关键总结

  • AutoResetEvent.WaitOne本身不会引发死锁,但在异步代码中混合使用阻塞调用(如WaitOne)和异步等待时,线程调度的时序差异会导致这类等待超时的现象。
  • 避免此类问题的最佳实践是:异步代码中尽量使用异步等待(如Task相关的API),而非阻塞式的同步原语调用。

内容的提问来源于stack exchange,提问作者Sky Nano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 19:12:30