1. 程式人生 > >多執行緒與UI操作

多執行緒與UI操作

前言

  為了讓程式儘快響應使用者操作,在開發Winform應用程式時經常會使用多執行緒,對於耗時的操作如果不使用多執行緒將會使UI介面長時間處於停滯狀態,這種情況是使用者非常不願意看到的,怎麼辦呢?用多執行緒。它可以很好的解決這個問題。下面是使用多執行緒操作介面UI的程式碼:

private void btnTest_Click(object sender, EventArgs e)
{
    Thread thread = new Thread(Run);
    thread.Start();
}

private void Run()
{
    txtContent.Visible = false
; }

  從上面的程式碼中可以看出,我希望單擊按鈕將txtContent文字框給隱藏掉。可是,當專案啟動時,程式碼卻丟擲System.InvalidOperationException異常,異常描述就是“執行緒間操作無效:從不是建立控制元件txtContent”的執行緒訪問它,如圖1-1所示。

這裡寫圖片描述
圖1-1 報錯頁面

問題原因

  之所以會出現這樣的情況,是因為在.Net中做了限制,不允許在除錯環境下使用執行緒訪問並非它自己建立的UI控制元件,這麼做可能是怕在多執行緒環境下對介面控制元件進行操作會出現不可預知的情況。

解決方案

  如果開發者可以確認自己的程式碼操作介面不會出現問題,可以用比較簡單的辦法解決,那就是設定CheckForIllegalCrossThreadCalls這個靜態屬性,它的預設是true,如果將其設為false的話,以後在多執行緒環境下操作介面再也不會丟擲異常了,我們上面的程式碼可以修改為:

private void btnTest_Click(object sender, EventArgs e)
{
    //指示是否對錯誤執行緒的呼叫,即是否允許在建立UI的執行緒之外的執行緒訪問
    CheckForIllegalCrossThreadCalls = false;
    Thread thread = new Thread(Run);
    thread.Start();
}

private void Run()
{
    txtContent.Visible = false;
}

  可是,畢竟微軟做出這個限制也不是為了難為你,它的存在肯定是有道理的,上面的操作會允許程式中所有的建立控制元件之外的執行緒操作控制元件,這種方式可能會給專案帶來未知的風險。那麼,怎麼降低這種風險呢?我們也可以設定某一個執行緒可以訪問,而其他的還是不可以訪問。

利用Invoke方法來操作介面

private void btnTest_Click(object sender, EventArgs e)
{
    Thread thread = new Thread(Run);
    thread.Start();
}
private void Run()
{
    /*
     * InvokeRequired,獲取一個值,該值指示呼叫方在對控制元件進行方法呼叫時是否必須呼叫Invoke方法,
     * 因為呼叫方位於建立控制元件所在的執行緒以外的執行緒中。
     */
    if (InvokeRequired)
    {
        /*
         * Action,可以使用此委託以引數形式傳遞方法,而不用顯式宣告自定義的委託,
         * 封裝的方法必須與此委託定義的方法簽名相對應,
         * 也就是說,封裝的方法不得具有引數,並且不得返回值,通常這種方法用於執行某個操作。
         */
        //delegate() { txtContent.Visible = false; },無參無返回值的匿名方法
        this.Invoke(new Action(delegate() { txtContent.Visible = false; }));
    }
    else
    {
        txtContent.Visible = false;
    }

}

  上面執行的操作是將文字框隱藏,而如果我們需要為其賦值的話,就需要有引數了,那麼用Action就不可以了,只能顯式定義一個委託來做,程式碼如下:

private delegate void SetName(string name);

private void btnTest_Click(object sender, EventArgs e)
{
    Thread thread = new Thread(Run);
    thread.Start();
}

private void Run()
{
    if (InvokeRequired)
    {
        this.Invoke(new SetName(SetTextName), "jujianfei");
    }
    else
    {
        txtContent.Visible = false;
    }            
}

private void SetTextName(string name)
{
    txtContent.Text = name;
}

  另外,還可以用BackgroundWorker類操作介面,不過這裡不做研究,有興趣的可以看本篇部落格的參考文章。

總結

  多執行緒程式設計任重道遠,點滴積累方可修成正果。文章如有不當之處,還望不吝賜教,多謝。