天天看點

WPF 異步程式設計

DispatherObject

很多wpf 對象有線程相關性(thread affinity),意味在你隻能在建立它的線程上使用它。這和window form的UI控件一樣。 WinForm的control.invoke() ,winform.timer 會在進行UI操作時做一個線程切換。

在wpf中,繼承自System.Windows.Threading的DispatherObject的對象都具有線程相關性, 比如:Brush, Geometry

public class DependencyObject : DispatcherObject

public abstract class DispatcherObject

{

    // Fields

    private Dispatcher _dispatcher;

    // Methods

    protected DispatcherObject();

    [EditorBrowsable(EditorBrowsableState.Never)]

    public bool CheckAccess();

    [FriendAccessAllowed]

    internal void DetachFromDispatcher();

    [EditorBrowsable(EditorBrowsableState.Never)]

    public void VerifyAccess();

    // Properties

    [EditorBrowsable(EditorBrowsableState.Advanced)]

    public Dispatcher Dispatcher { get; }

}

CheckAccess()與VeriftyAccess()來判斷是否在正确的線程上,VerifyAccess會抛異常,如果不在正确的線程上。Freezable對象能夠通過frozon,來消除線程的限制。

The Dispather:

每一個需要建立UI objects的線程必須要有一個dispatcher object.這個對象做的事相當于win32的消息泵,用一個循環不斷dispatch input message to appropriate handlers.

 DispatcherObject中有這個屬性,或者可以通過Dispatcher.CurrentDispather來獲得目前線程的dispather.

如果要做完一些事情後更新UI,必須保證在UI線程上更新。Dispatcher 就是提供這種功能的,Dispatcher.Invoke()方法 會等待方法執行完再傳回,Dispather.Invoke()方法不會等待方法執行完,而是立即傳回。Invoke()方法有死鎖風險,BeginInvoke 沒有。

匿名方法:

Public class window1:window

{

     Public void delegate Mymethod;

     Mymethod method = delegate{ this.foreground = new SolidColorBrush(Color.Blue);}

This.Dispather.BeginInvoke(DispatcherPriority.Normal,method);

} //有ApplicationIdle 和SystemIdle優先級

;

}

傳參數的方法:delegate void BackGround(Color c)

Private void SetBackgroundColor(Color c) {this.background = c;}

BackGround method = SetBackgroundColor;

DispatcherOperation e =This.dispacher.BeginInvoke(DispatherPriority.Normal,method,Color.Blue);

傳回的 DispatherOperation.Status屬性可以檢視狀态:

DispatcherOperationStatus

Value Meaning
Pending The dispatcher has not yet called the method.
Executing The method is currently executing on the dispatcher thread.
Completed The method has finished executing.
Aborted The operation was aborted.

可以用DispatherOperation.Abort()退出,不要DispatherOperation.Completed+=delegate{}; 因為當加上的時候可能線程可能已經傳回了。直接把完成時要做的事,放到線程執行方法中去,特别是界面處理。

沒有了.net異步程式設計的,EndInvoke()方法,也沒有把一個completion callback傳給BeginInvoke的 方式。

DispatcherTimer:

WPF 的timer,

private DispatcherTimer dt;

    public MyWindow( ) {

        DispatcherTimer dt = new DispatcherTimer( );

        dt.Tick += dt_Tick;

        dt.Interval = TimeSpan.FromSeconds(2);

        dt.Start( );

    }

    void dt_Tick(object sender, EventArgs e) {

        Random r = new Random( );

        byte[] vals = new byte[3];

        r.NextBytes(vals);

        Color c = Color.FromRgb(vals[0], vals[1], vals[2]);

        this.Background = new SolidColorBrush(c);

    }

private DispatcherTimer _timer;

timer = new DispatcherTimer(DispatcherPriority.Background);

timer.Interval = TimeSpan.FromMinutes(5);

timer.Tick += delegate { ScheduleUpdate(); };

timer.Start();

多UI thread和dispather

一個Application能有多個線程來建立UI,但是一個視窗和它的element都必須在一個線程中,每一個host UI object的線程都需要一個dispatcher,讓UI 對象來工作。在一個單線程的application,我們不需要建立dispather,因為application類會自動在startup時建立dispatcher,在Exit時候shut down dispatcher.但是如果我們要自己建多線程UI,就得自己start up和shut down Dispather.

ThreadStart threadMethod = delegate {

    Window1 w = new Window1( );

    w.Show( );

    System.Windows.Threading.Dispatcher.Run( ); // Won't return until dispatcher shuts down

};

Thread thread = new Thread(threadMethod);

thread.SetApartmentState(ApartmentState.STA);

thread.Start( );

繼承DispatcherObject基類會在建立時自動建dispatcher執行個體,是以不需要new,但是要Dispatcher.Run()來使得message能夠正确發送到UI object的線程中。這個方法不會傳回知道dispatcher.InvokeShutdown. 對于客戶自己建立的UI線程,需要手動shut down。

set the COM threading model to STA線程套間,雖然在com互動時候才用到COM threading model, 但是因為太多的系統特征需要com互動,是以dispatcher要求設定STA。

BackgroundWorker:

System.ComponentModel.BackgroundWorker, 這個類可以在windows forms和WPF中用,但是卻是兩種不同的實作機制。用AsyncOperationManager來區分,一個application層的變量,當WPF application start up時候,WPF會自動配置AsyncOperationManager,(用目前dispatcher).

WPF的BackgroundWorker實際上是為了更友善使用,對dispatcher的包裝。

使用:

1.      Attach event to DoWork event.

2.      Add handler to ProgressChanged, RunWorkerCompleted event

3.      為了使用progressChanged event,要設定BackgroundWorker.WorkerReportsProgress 為true; 在DoWork的handler中,調ReportProgress()報告狀态。

例子:

partial class MyWindow : Window {

    private BackgroundWorker bw;

    public MyWindow( ) {

        bw = new BackgroundWorker( );

        bw.DoWork += new DoWorkEventHandler(bw_DoWork);

        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);

        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(

                                                 bw_RunWorkerCompleted);

        bw.WorkerReportsProgress = true;

        bw.RunWorkerAsync( );

    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {

        // Running on a worker thread

        for (int i = 0; i < 10; ++i) {

            int percent = i * 10;

            bw.ReportProgress(percent);

            Thread.Sleep(1000);

        }

    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e) {

        this.Text = "Working: " + e.ProgressPercentage + "%";

    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

        this.Text = "Finished";

    }

當運作RunWorkerAsync( ),BackgroundWorker 在worker thread中抛出DoWork event,是以在DoWork handler中不能進行UI操作。但是ProgressChanged and RunWorkerCompleted events 是在UI thread中raise的,在它們的handler中可以進行UI 操作。

RunWorkerCompleted handler 傳入RunWorkerCompletedEventArgs object,DoWork m方法中可能抛出異常,如果有異常抛出,RunWorkerCompletedEventArgs 對象會包含異常,否則為null。