2017-08-29 178 views
3

编辑: 请参阅问题历史记录,对于未更改的问题以便使注释无效。当从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); 
       } 
      }); 
     } 

enter image description here

enter image description here

+0

您的主线程必须阻止某处,所以锁定没有被释放。找出那里。顺便说一句:你真的*开始*线程? – Fildor

+4

锁太多。这可能表明缺乏知识,但它[很容易解决](http://www.albahari.com/threading/)。您没有显示足够的代码: 谁调用了'TaskExtractBachFiles'? – Sinatr

+0

@Sinatr你可能是对的,但是每个方法都可以在其他地方调用,我从一个方法调用每个方法,你认为哪个片断可能会丢失? –

回答

4
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无法控制其生命周期,当用户关闭其托管的表单时,它将被丢弃。但是,这与工作线程执行完全不同步,即使控件作为门卫死了,它也会一直调用BeginInvoke。这会让你的程序炸弹,希望在ObjectDisposedException。一个锁无法解决的线程竞争错误。表格必须提供帮助,它必须主动阻止用户关闭它。关于this Q+A中的这个错误的一些注意事项。

为了完整起见,我应该提到第三种最常见的线程错误,这样的代码可能会受到影响。它没有一个正式的名字,我把它称为“流水虫”。它发生在工作线程经常调用BeginInvoke时,给UI线程太多的工作要做。容易发生,每秒调用超过大约千次就足够了。 UI线程开始刻录100%的核心,试图跟上调用请求并永远无法赶上。很容易看到,它会停止自己绘画并响应输入,而执行的任务的优先级较低。这需要以合理的方式进行修正,每秒更新UI超过25次只会产生人眼无法观察的模糊现象,因此毫无意义。