C#死锁调用锁定的方法
当我点击rbtn1,然后点击rbtn2时,下面的代码导致死锁。Rbtn2是异步的,当我只多次单击rbtn1时,就可以了。Rbtn2同步,当我只多次单击Rbtn2时,它也可以。但当我混合它们时,就会出现死锁。为什么会这样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
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来修复它。