什么导致死锁?

5

我在一段代码中遇到了死锁问题。幸运的是,我已经能够在以下示例中复现该问题。运行为普通的.Net Core 2.0控制台应用程序。

class Class2
{

    static void Main(string[] args)
    {
        Task.Run(MainAsync);
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }

    static async Task MainAsync()
    {
        await StartAsync();
        //await Task.Delay(1);  //a little delay makes it working
        Stop();
    }


    static async Task StartAsync()
    {
        var tcs = new TaskCompletionSource<object>();
        StartCore(tcs);
        await tcs.Task;
    }


    static void StartCore(TaskCompletionSource<object> tcs)
    {
        _cts = new CancellationTokenSource();
        _thread = new Thread(Worker);
        _thread.Start(tcs);
    }


    static Thread _thread;
    static CancellationTokenSource _cts;


    static void Worker(object state)
    {
        Console.WriteLine("entering worker");
        Thread.Sleep(100);  //some work

        var tcs = (TaskCompletionSource<object>)state;
        tcs.SetResult(null);

        Console.WriteLine("entering loop");
        while (_cts.IsCancellationRequested == false)
        {
            Thread.Sleep(100);  //some work
        }
        Console.WriteLine("exiting worker");
    }


    static void Stop()
    {
        Console.WriteLine("entering stop");
        _cts.Cancel();
        _thread.Join();
        Console.WriteLine("exiting stop");
    }

}

我的期望是完整的步骤序列,包括以下内容:

Press any key...
entering worker
entering loop
entering stop
exiting worker
exiting stop

然而,实际序列在Thread.Join调用时停顿:
Press any key...
entering worker
entering stop

最后,如果我在MainAsync中插入一个小延迟,一切都会顺利进行。

为什么(在哪里)会出现死锁?

注:在原始代码中,我使用了SemaphoreSlim而不是TaskCompletionSource,一切都没有问题。我只想理解问题出在哪里。


1
你为什么要混合使用 TaskThread - vgru
将代码更改为 var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);,它将按预期运行。现在 SetResult 同步运行连续操作,并且连续操作包括对 Stop() 的调用,该方法本身会加入执行 SetResult 的线程,因此它们会发生死锁。 - Evk
@Groo 在原始代码中有一个长时间运行的线程(工作线程),它执行多个任务而不混淆任务。然而,有一个可等待的函数暴露出来启动工作线程,当完成一些初始工作时就会完成。 - Mario Vernari
2个回答

3

Worker() 中的 tcs.SetResult(null); 调用将一直等待,直到底层任务完成(有关详细信息,请查看此问题)。在您的情况下,任务状态为 WaitingForActivation,这就是为什么会发生死锁的原因:

  1. 执行 Worker() 的线程被 tcs.SetResult(null) 调用阻塞。

  2. 执行 Stop() 的线程被 _thread.Join() 调用阻塞。


感谢您的帮助! - Mario Vernari

0

因为MainAsync()线程比其他线程“快”。 而且你只对任务进行控制,而不是对线程进行控制!

在你的方法MainAsync()中,你等待方法StartAsync()完成其工作,然后启动线程。一旦方法StartAsync()完成其工作(创建并启动线程),该函数会通知MainAsync()完成其工作。然后MainAsync()调用Stop方法。 但是你的线程在哪里?它在没有任何控制的情况下并行运行,并尝试完成其工作。这不是死锁,任务和线程之间没有同步。

这就是为什么当你放置await Task.Delay(1)时,你的代码可以工作,因为线程足够快,在任务结束之前完成工作(线程.join)。


嗯...首先,StartAsync方法会在SetResult方法释放TaskCompletionSource时结束,并且这是在线程内部完成的。其次,除非标记为取消,否则线程不应该结束(这是在Stop方法中完成的)。 - Mario Vernari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接