从UI调用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次只会产生人眼无法观察到的模糊,因此毫无意义。