2009-01-19 47 views
11

我當前正在嘗試編寫一個組件,其中的某些組件應該在UI線程上運行(解釋將會很長)。 所以最簡單的方法是將控件傳遞給它,並對其使用InvokeRequired/Invoke。 但我不認爲這是一個好的設計將控制引用傳遞給「數據/背景」組件,所以我正在尋找一種方法來在UI線程上運行代碼,而不需要控制可用。 喜歡的東西Application.Dispatcher.Invoke在WPF ...在不存在控制對象的UI線程上運行代碼

任何想法, THX 馬丁

+6

請註明如果你解決了你的問題,你會接受一個答案。 – SandRock 2012-02-24 15:05:39

回答

2

你是正確的,這是不好控制傳遞給線程。 Winforms控件是單線程的,將它們傳遞給多個線程可能會導致競爭條件或破壞UI。相反,您應該讓UI的線程功能可用,並在UI已經準備就緒時讓它調用線程。如果您想讓後臺線程觸發UI更改,請從UI中公開後臺事件並訂閱。線程可以隨時觸發事件,並且UI可以在可以時響應它們。

在不阻塞UI線程的線程之間創建這種雙向通信是很多工作。下面是一個使用一個BackgroundWorker類高度縮寫示例:

public class MyBackgroundThread : BackgroundWorker 
{ 
    public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething; 

    public MyStatus TheUIWantsToKnowThis { get { whatever... } } 

    public void TheUIWantsMeToDoSomething() 
    { 
     // Do something... 
    } 

    protected override void OnDoWork(DoWorkEventArgs e) 
    { 
     // This is called when the thread is started 
     while (!CancellationPending) 
     { 
      // The UI will set IWantTheUIToDoSomething when it is ready to do things. 
      if ((IWantTheUIToDoSomething != null) && IHaveUIData()) 
       IWantTheUIToDoSomething(this, new ClassToPassToUI(uiData)); 
     } 
    } 
} 


public partial class MyUIClass : Form 
{ 
    MyBackgroundThread backgroundThread; 

    delegate void ChangeUICallback(object sender, ClassToPassToUI uiData); 

    ... 

    public MyUIClass 
    { 
     backgroundThread = new MyBackgroundThread(); 

     // Do this when you're ready for requests from background threads: 
     backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI); 

     // This will run MyBackgroundThread.OnDoWork in a background thread: 
     backgroundThread.RunWorkerAsync(); 
    } 


    private void UserClickedAButtonOrSomething(object sender, EventArgs e) 
    { 
     // Really this should be done in the background thread, 
     // it is here as an example of calling a background task from the UI. 
     if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests) 
      backgroundThread.TheUIWantsMeToDoSomething(); 

     // The UI can change the UI as well, this will not need marshalling. 
     SomeoneWantsToChangeTheUI(this, new ClassToPassToUI(localData)); 
    } 

    void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData) 
    { 
     if (InvokeRequired) 
     { 
      // A background thread wants to change the UI. 
      if (iAmInAStateWhereTheUICanBeChanged) 
      { 
       var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI); 
       Invoke(callback, new object[] { sender, uiData }); 
      } 
     } 
     else 
     { 
      // This is on the UI thread, either because it was called from the UI or was marshalled. 
      ChangeTheUI(uiData) 
     } 
    } 
} 
+1

我更喜歡這種方法,因爲它乾淨地將UI代碼與後臺工作分開。它還允許多個「監聽者」響應後臺工作狀態更新。 – 2013-11-25 21:28:45

1

把UI操作的方法中的形式被操縱,並通過一個委託給在後臺線程運行的代碼,點菜APM。您不必使用params object p,您可以強制鍵入以適應您自己的目的。這只是一個簡單的通用示例。

delegate UiSafeCall(delegate d, params object p); 
void SomeUiSafeCall(delegate d, params object p) 
{ 
    if (InvokeRequired) 
    BeginInvoke(d,p);   
    else 
    { 
    //do stuff to UI 
    } 
} 

這種方法是基於這樣一個事實,即代表引用特定實例上的方法;通過使實現成爲表單的一種方法,您將表單帶入範圍爲this。以下語義相同。

delegate UiSafeCall(delegate d, params object p); 
void SomeUiSafeCall(delegate d, params object p) 
{ 
    if (this.InvokeRequired) 
    this.BeginInvoke(d,p);   
    else 
    { 
    //do stuff to UI 
    } 
} 
1

怎麼樣傳遞一個System.ComponentModel.ISynchronizeInvoke?這樣你可以避免傳遞一個Control。

18

有做這更好的,更抽象的方式,在兩個WinForms和WPF的工作原理:

System.Threading.SynchronizationContext.Current.Post(theMethod, state); 

這工作,因爲WindowsForms安裝了一個WindowsFormsSynchronizationContext對象作爲當前同步上下文。 WPF做類似的事情,安裝它自己的專用同步上下文(DispatcherSynchronizationContext)。

.Post對應於control.BeginInvoke.Send對應於control.Invoke

+0

當我嘗試調用它時,SynchronizationContext.Current爲null ... D: – 2013-03-15 19:55:48

2

首先,在您的表單構造函數中,保留SynchronizationContext.Current對象(實際上是WindowsFormsSynchronizationContext)的類作用域引用。

public partial class MyForm : Form { 
    private SynchronizationContext syncContext; 
    public MyForm() { 
     this.syncContext = SynchronizationContext.Current; 
    } 
} 

然後,你的類中的任何位置,使用此背景下將消息發送到用戶界面:

public partial class MyForm : Form { 
    public void DoStuff() { 
     ThreadPool.QueueUserWorkItem(_ => { 
      // worker thread starts 
      // invoke UI from here 
      this.syncContext.Send(() => 
       this.myButton.Text = "Updated from worker thread"); 
      // continue background work 
      this.syncContext.Send(() => { 
       this.myText1.Text = "Updated from worker thread"; 
       this.myText2.Text = "Updated from worker thread"; 
      }); 
      // continue background work 
     }); 
    } 
} 

您將需要以下擴展方法與lambda表達式的工作:http://codepaste.net/zje4k6