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