2017-08-29 246 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次只會產生人眼無法觀察的模糊現象,因此毫無意義。