[英]Lock hangs when called from UI to System.Threading.Thread
编辑:请查看问题历史记录,以保持未解决的问题,以免使评论无效。
我点击执行某些代码的按钮,它创建一个线程(System.Threading.Thread)。 当我重新点击开始进程的按钮时,它会挂起并冻结ui。 可能是什么原因?
public partial class ucLoader : UserControl
{
//lock object for whole instance of class ucLoader
private object lockUcLoader = new object();
//bringing info from ui
private void btnBringInfo_Click(object sender, EventArgs e)
{
lock (lockUcLoader)
{
btnBringInfo_PerformClick(false);
}
}
//using this method because it could be called when even button not visible
internal void btnBringInfo_PerformClick(bool calledFromBandInit)
{
lock (lockUcLoader) //HANGS HERE when called multiple times and ui freeze as well
//by the way I am using (repetitive) lock, because this method also called independently from btnBringInfo_Click
{
//...
this.btnLoad_PerformClick();
}
}
//Another button perform click that could be triggered elsewhere when even button not visible
private void btnLoad_PerformClick()
{
lock (lockUcLoader) //I am using (repetitive) lock, because this method also called independently from btnBringInfo_PerformClick
{
//...
Run();
}
}
//method for creating thread which System.Threading.Thread
private void Run()
{
lock (lockUcLoader) //Maybe this lock is NOT REQUIRED, as it is called by only btnLoad_PerformClick(), could you please confirm?
{
//some code that thread can be killed when available, you can ingore this two lines as they are irrelevant to subject, I think
Source = new CancellationTokenSource();
Token = Source.Token;
var shell = new WindowsShell();
Thread = new Thread((object o) =>
{
//...
var tokenInThread = (CancellationToken)o;
exitCode =TaskExtractBatchFiles(cls, shell, exitCode);
using (var logEnt = new logEntities())
{
//Do some db operation
//...
this.Invoke((MethodInvoker)delegate
{
//do some ui update operation
//...
});
}
}
Thread.Start(Token);
}
}
public void Progress(string message)
{
Invoke((MethodInvoker)delegate //ATTENTION HERE see below picture Wait occurs here
{
if (message != null && message.Trim() != string.Empty)
{
this.txtStatus.AppendText(message + Environment.NewLine);
}
});
}
}
为了避免得到封闭的问题,我的问题是如何防止以下方法可以从后台线程和ui线程中锁定出来
public void Progress(string message)
{
Invoke((MethodInvoker)delegate //ATTENTION HERE see below picture Wait occurs here
{
if (message != null && message.Trim() != string.Empty)
{
this.txtStatus.AppendText(message + Environment.NewLine);
}
});
}
Invoke((MethodInvoker)delegate ...
每当你在代码中使用lock
语句时,你总是冒着引发死锁的风险。 其中一个经典的线程错误。 您通常需要至少两个锁才能到达那里,以错误的顺序获取它们。 是的,你的计划中有两个。 一个你宣称自己。 而且你看不到它,因为它被埋在管道内,使得Control.Invoke()工作。 无法看到锁定是导致死锁成为调试难题的原因。
你可以推断出来,Control.Invoke中的锁是必要的,以确保工作线程被阻塞,直到UI线程执行委托目标。 可能还有助于推断出该程序陷入僵局的原因。 您启动了工作线程,它获取了lockUcLoader
锁并开始执行其工作,同时调用Control.Invoke。 现在,在工作完成之前单击按钮,它必然会阻止。 但这使得UI线程变得紧张,并且不再能够执行Control.Invoke代码。 因此工作线程挂起在Invoke调用上,它不会释放锁。 并且UI线程永远挂在锁上,因为工作者无法完成,死锁城市。
Control.Invoke来自.NET 1.0,这是一个框架版本,在与线程相关的代码中有几个严重的设计错误。 虽然本来是有帮助的,但他们只是为程序员设置了陷入困境的死亡陷阱。 Control.Invoke的独特之处在于它永远不会正确使用它。
区分Control.Invoke和Control.BeginInvoke。 只有在需要返回值时才需要调用。 注意你不这样做,使用BeginInvoke代替足够好并立即解决死锁问题。 您可以考虑使用Invoke从UI获取值,以便在工作线程中使用它。 但这导致了其他主要的线程问题,一个线程竞争错误,工作者不知道UI处于什么状态。比如说,用户可能正在忙着与它进行交互,键入一个新值。 你无法知道你获得了什么价值,它很容易成为过时的旧价值。 不可避免地在UI和正在完成的工作之间产生不匹配。 避免这种不幸事故的唯一方法是阻止用户键入新值,使用Enable = false轻松完成。 但是现在使用Invoke不再有意义,你可以在启动线程时传递值。
因此,使用BeginInvoke已足以解决问题。 但那不是你应该停下来的地方。 在Click事件处理程序中没有任何关键点,他们所做的就是让UI无响应,这极大地困扰了用户。 您必须执行的操作是将这些按钮的启用属性设置为false 。 完成工作后将它们设置为true 。 现在它不会再出错了,你不需要锁,用户可以得到很好的反馈。
还有另一个严重的问题,你还没有遇到,但你必须解决。 UserControl无法控制其生命周期,当用户关闭托管它的表单时,它会被释放。 但是这与工作线程执行完全不同步,即使控件作为一个doornail失效,它也会一直调用BeginInvoke。 这将使你的程序炸弹,希望在ObjectDisposedException上。 锁无法解决的线程竞争错误。 表单必须有所帮助,它必须主动阻止用户关闭它。 关于这个Q + A中的这个错误的一些注释。
为了完整性,我应该提到第三个最常见的线程错误,这样的代码可能会受到影响。 它没有官方名称,我称之为“firehose bug”。 它发生在工作线程经常调用BeginInvoke时,给UI线程做太多工作。 容易发生,每秒调用它超过一千次就足够了。 UI线程开始烧录100%核心,试图跟上调用请求,永远无法赶上。 容易看到,它停止绘画本身并响应输入,以较低优先级执行的职责。 这需要以逻辑方式修复,每秒更新UI超过25次只会产生人眼无法观察到的模糊,因此毫无意义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.