目錄
介紹
通常,Windows窗體和控件本身不是線程安全的,這會使多線程應用程序中的事情大大複雜化。本文提供了一種線程同步的替代方法,使用編組來執行從主UI線程上的輔助線程調用的一些代碼。
概念化這個混亂
線程化很困難,而且很容易出錯,當存在同步問題時,它們很容易被追查。如果完全合理,通常我們應該在需要魯棒代碼的情況下儘可能避免多線程。
多線程會使事情複雜化的一種非常常見的情況是更新用戶界面,由於Winforms線程不安全,因此通常必須從UI線程完成。
我們將通過將一些代碼從調用線程編組到主UI線程,從而使代碼本身在UI線程上執行,從而完全避免同步問題。由於所有與UI相關的工作都在UI線程上完成,因此沒有同步問題。
輸入ISynchronizeInvoke。這個小接口是一個約定,上面寫着“嘿,我可以接受委託並在此線程上執行委託”,其中“this thread”是在其ISynchronizeInvoke上創建實現的線程。由於每種形式和每個控件都實現此接口,因此每個控件都可以將委託代碼編組到其線程(UI線程)。我們將看到這非常有用。
編碼此混亂
一旦掌握了這些代碼,它就非常基礎。
ISynchronizeInvoke 實現四個重要成員:
- Invoke() (兩個重載)在創建此實例的線程上調用給定委託中的代碼
- BeginInvoke()和EndInvoke()是上述的異步版本——BeginInvoke(),不像Invoke()不會阻塞。
- InvokeRequired表示我們是否必須使用Invoke()/ BeginInvoke()安全執行。如果爲假,則不必整理代理,我們可以直接調用它。這唯一的好處就是性能。不一定必須使用它。
讓我們看一下源代碼:
// create a thread. we use this instead of tasks
// or thread pooling simply for demonstration.
// normally, it's not a great idea to spawn
// threads directly like this.
var thread = new Thread(() => {
// fill the progress bar, slowly
for(var i = 0;i<10001;++i)
{
// update the progress bar in a thread safe manner
// you can't use a lambda here without assigning
// it to a specific delegate type. Here, we use
// Action since it doesn't need any arguments
// or a return value
Action action = () =>
{
// execute this code on the UI thread
Progress.Value = i / 100;
};
// this marshals the code above onto the thread
// that this form is running on (the UI thread)
// everything within the action code is executed
// on the UI thread. We only do this if
// InvokeRequired is true, otherwise we can
// invoke the delegate directly. In this example,
// it should always be true, but in the real world,
// it will not be necessarily. Calling Invoke when
// it's not necessary (InvokeRequired=false) doesn't
// hurt, but it causes unnecessary overhead.
if (InvokeRequired)
Invoke(action);
else
action();
// all controls and forms implement Invoke.
// There is also BeginInvoke/EndInvoke which
// you can use to invoke asynchronously
}
});
thread.Start();
基本上,我們在這裏正在執行的是生成一個線程,並且我們希望該線程在進度條上進行更新。但是,進度條位於UI線程上,因此從另一個線程進行調用並不安全。
爲了解決這個問題,而不是實現線程同步,我們可以簡單地按上述方式使用Invoke(),這將使用窗體的ISynchronizeInvoke.Invoke()方法在UI線程上執行action中的所有代碼。請注意,我們不能直接使用Invoke()或lambda或匿名委託BeginInvoke()。這就是爲什麼我們把它放在一個名爲action的Action委託中。當然,這有點笨拙,但是與必須對UI實現線程安全調用相比,它有了很大的改進。