簡體   English   中英

從UI調用System.Threading.Thread時鎖定掛起

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM