.NET設計篇08-線程取消模型和跨線程訪問UI

知識須要不斷積累、總結和沉澱,思考和寫做是成長的催化劑,輸出倒逼輸入web

內容目錄

1、線程統一取消模型一、取消令牌二、能夠中斷的線程一、設計一箇中斷函數二、建立CancellationTokenSource對象三、啓動線程四、取消線程執行2、跨線程訪問UI基本方法一、Control.Invoke和BeginInvoke二、桌面退出三、編寫線程安全的控件3、BackgroundWorker組件一、幹活的代碼二、啓動任務三、結果取回四、取消任務五、進度報告4、等等數組

1、線程統一取消模型

線程取消在多線程開發中很是廣泛,鑑於此微軟在.NET4.0基礎類庫中引入線程統一取消模型對線程取消功能的支持。安全

一、取消令牌

線程統一取消模型中兩個重要的類型就是CancellationToken和CancellationTokenSource
每一個CancellationTokenSource對象都包容着一個「取消令牌(CancellationToken)」,並經過它的Token屬性向外界展露,用法也很簡單經過調用CancellationTokenSource的Cancel()方法通知取消令牌。下面有個小栗子多線程

二、能夠中斷的線程

下面介紹一個能夠中斷的線程模型流程app

一、設計一箇中斷函數

一個能夠中斷的線程函數模板看起來像下面這樣異步

public class ThreadFuncObject
{
    //經過構造函數從外界傳入取消令牌
    private CancellationToken _token;
    public ThreadFuncObject(CancellationToken token)
    
{
        _token = token;
    }
    public void DoWork()
    
{
        //各類功能代碼...
        if (_token.IsCancellationRequested)
        {
            //完成清理工做...
            //簡單的處理直接return便可
            //return;
            //建議拋出個取消異常
            throw new OperationCanceledException(_token);
        }
        //各類功能代碼...
    }
}
二、建立CancellationTokenSource對象
CancellationTokenSource tokenSource = new CancellationTokenSource();
三、啓動線程
ThreadFuncObject obj = new ThreadFuncObject(tokenSource.Token);
Thread th = new Thread(obj.DoWork);
th.Start();
四、取消線程執行
tokenSource.Cancel();

2、跨線程訪問UI基本方法

剛開始桌面程序開發時,在作進度條等通知UI更新的功能時,常常會遇到跨線程訪問UI的問題,拋的錯就是控件不能從不是建立它的線程去更改。ide

在.NET Framework中,全部的可視化控件都從System.Windows.Forms.Control類派生而來,考慮到跨線程訪問控件的須要,Control類提供了相應的方法完成跨線程更新界面。函數

一、Control.Invoke和BeginInvoke

//
// 摘要:
//     在擁有此控件的基礎窗口句柄的線程上執行指定的委託。
//
// 參數:
//   method:
//     包含要在控件的線程上下文中調用的方法的委託。
//
// 返回結果:
//     正在被調用的委託的返回值,或者若是委託沒有返回值,則爲 null。
public object Invoke(Delegate method);

Invoke方法的參數是一個委託,表明在建立控件的線程中要執行的方法。實際場景中是要向UI傳值的,可使用下面的重載ui

//
// 摘要:
//     在擁有控件的基礎窗口句柄的線程上,用指定的自變量列表執行指定委託。
//
// 參數:
//   method:
//     一個方法委託,它採用的參數的數量和類型與 args 參數中所包含的相同。
//
//   args:
//     做爲指定方法的參數傳遞的對象數組。 若是此方法沒有參數,該參數能夠是 null。
//
// 返回結果:
//     System.Object,它包含正被調用的委託返回值;若是該委託沒有返回值,則爲 null。
public object Invoke(Delegate method, params object[] args);

使用像下面這樣spa

private void ThreadMethod(Object info)
{
    Action<string> del = (msg) => lblInfo.Text = msg;
    lblInfo.Invoke(del,info);
}

Control.Invoke是同步方法,就是當工做線程調用此方法將一個方法委託給UI線程執行之後,它必須等待UI線程執行完此方法後才能繼續執行,若是UI線程很忙,工做線程可能要等待較長的時間不能工做。這可能不太合理

Control.BeginInvoke是異步方法,就是工做線程能夠將一個方法傳送給UI線程執行以後,繼續執行下一步的任務而無需等待。

UI線程是單一的,就是來更新用戶界面和接受用戶響應的。它從消息隊列中提取消息處理,若是某個消息執行時間較長,將會致使界面失去響應,感受死機了。因此長時間任務要交給獨立的工做線程去執行,UI線程只管向用戶展現執行的結果。UI線程不該兼職過多

二、桌面退出

桌面(Windows窗體)程序是事件驅動的,應該確保用戶不能頻繁的點擊啓動訪問控件的線程。由於多線程同時訪問同一個控件,可能會形成程序不穩定,出現意想不到的的結果。能夠經過控制按鈕狀態或線程狀態,控制線程同步。

若是關閉主窗體致使主線程退出,工做線程還沒結束。由於主窗體銷燬了,其上的控件都被銷燬,而工做線程還包含着訪問控件的代碼,因此會拋出ObjectDisposedException異常。

前幾篇線程中講到,最簡單的方法就是將工做線程設置爲後臺線程,幫隨着主線程的退出而退出,另外一個方法就是在窗體FormClosing關閉事件中,判斷工做線程的狀態,手動Abort終止它。

三、編寫線程安全的控件

咱們能夠將多線程訪問功能封裝進控件裏,從而簡化編寫跨線程訪問時的代碼。好比Lable標籤控件的text屬性不能跨線程直接訪問,能夠派生出一個新的ThreadSafeLable類,向下面這樣,這樣無論跨不跨線程,使用相同的代碼訪問線程安全的控件。

public class ThreadSafeLable : Label
{
    //覆蓋基類的Text屬性
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            if (InvokeRequired)//跨線程調用
            {
                Action<string> del = (msg) => base.Text = msg;
                Invoke(del, value);
            }
            else//普通調用
            {
                base.Text = value;
            }
        }
    }
}

項目開發中注意封裝一些這樣的控件,能夠簡化多線程調用代碼,提升項目的開發效率

3、BackgroundWorker組件

其實微軟提供了一個現成的組件用於跨線程訪問UI的,那就是BackgroundWorker組件,大大簡化了此類程序的開發。基於事件的異步調用模式,就是經過事件告知何時幹什麼事。支持報告進度,支持取消。

一、幹活的代碼

在DoWork事件中,真正幹活的代碼放在這裏

二、啓動任務

調用BackgroundWorker組件的RunWorkerAsync方法,此方法會激發DoWork事件。此方法有個重載能夠傳Object對象,在DoWork中經過DoWorkEventArgs.Argument來接收

三、結果取回

BackgroundWorker組件有一個RunWorkerCompleted事件
其中的RunWorkerCompletedEventArgs參數包含如下重要信息:

參數屬性 描述
e.Result 工做任務執行的結果
e.Error 這是一個Exception對象,若是工做任務執行過程當中沒有發生異常,則此屬性爲null,若是發生了異常,此屬性引用被拋出的異常對象
e.Cancelled 若是在工做任務完成前用戶取消了操做,則此屬性爲True,不然此屬性爲False

四、取消任務

BackgroundWorker組件有一個CancelAsync方法,調用此方法將會致使BackgroundWorker組件的只讀屬性CancellationPending爲True,而後在DoWork中判斷便可。

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bw = sender as BackgroundWorker;
    if (bw.CancellationPending)//若是用戶取消了操做
    {
        e.Cancel = true;//此結果將會傳到RunWorkerCompleted事件中
        return;//仍須要手動提交結束任務
    }
    //...
}

五、進度報告

BackgroundWorker組件有一個ProgressChanged事件。在DoWork事件處理代碼中合適的地方調用BackgroundWorker組件的ReportProgress方法,就會激發ProgressChanged事件。ReportProgress除了能夠報告進度,也能夠經過其重載報告一個object對象,每每是描述信息。

在ProgressChanged事件中使用線程同步上下文作了特殊處理,能夠直接訪問窗體上控件,無需考慮跨線程問題。

4、等等

好多東西之前都認真看過,沒記性就忘了。立刻要搬家了,裝不進腦子裏就帶不走。每次搬家還要爲幾本書多付一些搬家費,惆悵。(沒有你們電,書本就是最重的東西了)

給我一杯酒再給我一支菸,說走就走有緣就再見

相關文章
相關標籤/搜索