C#死锁调用锁定的方法

C#死锁调用锁定的方法,c#,winforms,asynchronous,deadlock,C#,Winforms,Asynchronous,Deadlock,当我点击rbtn1,然后点击rbtn2时,下面的代码导致死锁。Rbtn2是异步的,当我只多次单击rbtn1时,就可以了。Rbtn2同步,当我只多次单击Rbtn2时,它也可以。但当我混合它们时,就会出现死锁。为什么会这样 private void rbtn1_Click(object sender, EventArgs e) { Task.Run(() => UpdateDisplayLock("a")); } private void ra

当我点击rbtn1,然后点击rbtn2时,下面的代码导致死锁。Rbtn2是异步的,当我只多次单击rbtn1时,就可以了。Rbtn2同步,当我只多次单击Rbtn2时,它也可以。但当我混合它们时,就会出现死锁。为什么会这样

    private void rbtn1_Click(object sender, EventArgs e)
    {
        Task.Run(() => UpdateDisplayLock("a"));
    }

    private void radButton2_Click(object sender, EventArgs e)
    {
        UpdateDisplayLock("a");
    }

    private object _lockKey = new object();
    private void UpdateDisplayLock(string i)
    {
        lock (_lockKey)
        {
            Interlocked.Increment(ref _uniqueId);
            var uniqueId = _uniqueId;
            Invoke((Action)delegate
            {
                rlblDisplay.Text += Environment.NewLine + uniqueId + string.Format(":{0} Start;", i);
            });
            Thread.Sleep(5000);
            Invoke((Action)delegate
            {
                rlblDisplay.Text += Environment.NewLine + uniqueId + string.Format(":{0} End;", i);
            });
        }
    }

我如何解决这个问题?或者,异步和同步调用方法只是一种不好的做法?如果是这样的话,有没有办法限制带锁的方法只能异步使用

UpdateDisplayLock
中调用UI线程。因此,当您在按下第一个按钮后从另一个线程调用此方法时,它需要定期访问UI线程才能继续

当你点击第二个按钮调用
UpdateDisplayLock
时,它会点击
,因为后台进程正在等待它,它只会坐在那里等待。在第一个进程完成之前,您将阻塞UI线程(这样它就可以释放锁)

当运行
UpdateDisplayLock
的后台线程去调用UI线程中的操作时,它会坐在那里等待UI线程中安排的工作。第二个按钮点击在那里等待您,阻塞了UI线程

现在有两个线程,每个线程都在另一个线程上等待。僵局


至于如何解决这个问题,最好的解决方案是使
UpdateDisplayLock
成为一个固有的异步操作,而不是从另一个线程调用或不调用的固有同步操作:

private async Task UpdateDisplayLock(string i)
{
    _uniqueId++;
    var uniqueId = _uniqueId;
    rlblDisplay.Text += Environment.NewLine + 
        uniqueId + string.Format(":{0} Start;", i);
    await Task.Delay(TimeSpan.FromSeconds(5));
    rlblDisplay.Text += Environment.NewLine + 
        uniqueId + string.Format(":{0} End;", i);
}
注意,在这个实现中,它将允许多个调用交织它们的开始和结束调用,但是由于增量和UI操作都在UI线程中,因此不会有任何线程错误。如果您不希望任何后续调用在前一个调用完成之前能够启动其操作/日志记录,则可以使用
信号量lim
异步执行该操作:

private SemaphoreSlim semaphore = new SemaphoreSlim(1);
private async Task UpdateDisplayLock(string i)
{
    await semaphore.WaitAsync();
    try
    {
        _uniqueId++;
        var uniqueId = _uniqueId;
        rlblDisplay.Text += Environment.NewLine +
            uniqueId + string.Format(":{0} Start;", i);
        await Task.Delay(TimeSpan.FromSeconds(5));
        rlblDisplay.Text += Environment.NewLine +
            uniqueId + string.Format(":{0} End;", i);
    }
    finally
    {
        semaphore.Release();
    }
}
然后,如果您有事情要做,您可以
wait
这个异步方法形成您的事件处理程序,或者如果完成后不需要执行任何操作,您也可以调用它:

private async void rbtn1_Click(object sender, EventArgs e)
{
    await UpdateDisplayLock("a");
    DoSomethingElse();
}

private void radButton2_Click(object sender, EventArgs e)
{
    var updateTask = UpdateDisplayLock("a");
}

如果您按下按钮2,线程将进入睡眠状态并锁定,如果您现在按下按钮1,线程将进入锁定状态,保持GUI线程锁定(GUI现在已关闭),现在第一个线程(具有锁定)完成启动调用的睡眠语句。调用现在等待GUI线程运行操作。如果出现死锁,Invoke会阻止当前线程,直到GUI线程能够处理您的请求。这永远不会发生,因为您锁定了GUI线程

绕过死锁的最简单方法是使用BeginInvoke,它在操作完成之前不会阻塞当前线程。但仍然会导致奇怪的行为,因为GUI更新可能会在操作运行时延迟,从而导致意外行为


真正的问题在于您在GUI线程中运行了5秒钟的操作。这会导致糟糕的用户体验。以及将线程与调用混合时出现的问题。最好将UpdateDisplayLock实现为Async,并使用信号量LIM将多线程同步为Servy在其回答中发布的内容。

rbtn1\u单击不是异步的。您正在等待任务完成。调用时也不会返回任务结果,因此任务永远不会完成。@Alex它不会同步阻塞UI线程。大概这就是他的意思。顺便说一下,你应该
var uniqueId=Interlocked.Increment(ref\u uniqueId)相反,因为取决于代码的其余部分做什么,
var uniqueId=\u uniqueId可能会产生您不期望的值。不确定为什么需要使用
lock()
。有点违背了联锁API的目的。在消息泵上同步调用
时锁定通常会导致死锁,如果不小心,问题可能是只有一个线程操纵UI。如果您理解了这个概念,您的问题可能会变得更清楚,第二个Invoke()调用会出现死锁。调用是危险的,只有在需要返回值时才需要调用。通过使用BeginInvoke来修复它。