使用ISynchronizeInvoke無痛地創建線程安全用戶界面

目錄

介紹

概念化這個混亂

編碼此混亂


介紹

通常,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()。這就是爲什麼我們把它放在一個名爲actionAction委託中。當然,這有點笨拙,但是與必須對UI實現線程安全調用相比,它有了很大的改進。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章