2008-08-22 67 views
73

我發現.NET事件模型是這樣的,我經常會在一個線程上引發事件並在另一個線程上偵聽它。我想知道從後臺線程將事件編組到我的UI線程的最乾淨的方式是什麼。最乾淨的方式來調用跨線程事件

基於社區的建議,我用這個:

// earlier in the code 
mCoolObject.CoolEvent+= 
      new CoolObjectEventHandler(mCoolObject_CoolEvent); 
// then 
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    if (InvokeRequired) 
    { 
     CoolObjectEventHandler cb = 
      new CoolObjectEventHandler(
       mCoolObject_CoolEvent); 
     Invoke(cb, new object[] { sender, args }); 
     return; 
    } 
    // do the dirty work of my method here 
} 
+0

請記住,當現有託管控件尚未具有非託管句柄時,InvokeRequired可能會返回false。在控制完全建立之前將會提出的事件中應該謹慎行事。 – GregC 2009-04-29 17:30:21

回答

24

一對夫婦的意見:

  • 不要創建簡單的代表明確這樣的代碼,除非你是2.0以上,所以你可以使用:
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
       sender, 
       args); 
  • 此外,您不需要創建和填充對象數組,因爲args參數是「params」類型,因此您只需傳入列表即可。

  • 我可能會傾向於Invoke超過BeginInvoke因爲後者會導致代碼被異步調用這可能是也可能不是你追求的,但會使處理後續異常困難,但無EndInvoke調用傳播。會發生什麼事是您的應用程序將最終得到TargetInvocationException

11

我順多餘的委託聲明。

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args); 
     return; 
    } 
    // do the dirty work of my method here 
} 

對於非事件,你可以使用System.Windows.Forms.MethodInvoker委託或System.Action

編輯:此外,每個事件都有相應的EventHandler委託,因此根本不需要重新聲明一個委託。

+1

對我來說,它是這樣工作的:`Invoke(new Action (mCoolObject_CoolEvent),sender,args);` – 2015-08-18 16:28:06

+0

@ToniAlmeida是的,這是我的代碼中的拼寫錯誤。感謝您指出。 – 2015-08-18 17:07:40

0

我一直想知道它是多麼昂貴的總是假設,要求調用...

private void OnCoolEvent(CoolObjectEventArgs e) 
{ 
    BeginInvoke((o,e) => /*do work here*/,this, e); 
} 
+1

在GUI線程中執行BeginInvoke會導致有問題的操作被推遲,直到下一次UI線程處理Windows消息。在某些情況下,這實際上可能是有用的。 – supercat 2011-04-17 22:02:54

0

您可以嘗試開發一些那種接受SynchronizationContext作爲輸入,並使用它來調用事件的通用組件。

2

作爲一個有趣的方面說明,WPF的綁定自動處理封送處理,因此您可以將UI綁定到在後臺線程上修改的對象屬性,而無需執行任何特殊操作。這已經證明對我來說是一個很好的節省時間。

在XAML:

<TextBox Text="{Binding Path=Name}"/> 
+0

這不會工作。一旦你在非UI線程上設置道具,你會得到例外。即:Name =「gbc」砰!失敗......沒有免費的奶酪隊友 – 2011-11-23 11:40:22

+0

這不是免費的(它花費執行時間),但是wpf綁定機器確實似乎自動處理跨線程編組。我們使用了很多道具,這些道具通過後臺線程收到的網絡數據進行更新。這裏有一個解釋:http://blog.lab49.com/archives/1166 – gbc 2011-11-27 21:43:30

40

我有some code for this在線。它比其他建議好得多;絕對檢查出來。

使用範例:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    // You could use "() =>" in place of "delegate"; it's a style choice. 
    this.Invoke(delegate 
    { 
     // Do the dirty work of my method here. 
    }); 
} 
3

我覺得最徹底的方法是絕對去AOP路線。創建幾個方面,添加必要的屬性,並且不必再次檢查線程關聯。

2

我做了以下「通用」跨線程調用類爲我自己的目的,但我認爲這是值得分享:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Forms; 

namespace CrossThreadCalls 
{ 
    public static class clsCrossThreadCalls 
    { 
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value); 
    public static void SetAnyProperty(Control c, string Property, object Value) 
    { 
     if (c.GetType().GetProperty(Property) != null) 
     { 
     //The given property exists 
     if (c.InvokeRequired) 
     { 
      SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty); 
      c.BeginInvoke(d, c, Property, Value); 
     } 
     else 
     { 
      c.GetType().GetProperty(Property).SetValue(c, Value, null); 
     } 
     } 
    } 

    private delegate void SetTextPropertyCallBack(Control c, string Value); 
    public static void SetTextProperty(Control c, string Value) 
    { 
     if (c.InvokeRequired) 
     { 
     SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty); 
     c.BeginInvoke(d, c, Value); 
     } 
     else 
     { 
     c.Text = Value; 
     } 
    } 
    } 

而且你可以簡單地從另一個線程使用SetAnyProperty():

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString()); 

在這個例子中,上面的KvaserCanReader類運行自己的線程,並調用主窗體上的lb_Speed標籤的text屬性。

2

如果要將結果發送到UI線程,請使用同步上下文。我需要改變線程優先級,所以我改變了使用線程池線程(註釋掉代碼)並創建了自己的新線程。我仍然可以使用同步上下文來返回數據庫是否成功取消。

#region SyncContextCancel 

    private SynchronizationContext _syncContextCancel; 

    /// <summary> 
    /// Gets the synchronization context used for UI-related operations. 
    /// </summary> 
    /// <value>The synchronization context.</value> 
    protected SynchronizationContext SyncContextCancel 
    { 
     get { return _syncContextCancel; } 
    } 

    #endregion //SyncContextCancel 

    public void CancelCurrentDbCommand() 
    { 
     _syncContextCancel = SynchronizationContext.Current; 

     //ThreadPool.QueueUserWorkItem(CancelWork, null); 

     Thread worker = new Thread(new ThreadStart(CancelWork)); 
     worker.Priority = ThreadPriority.Highest; 
     worker.Start(); 
    } 

    SQLiteConnection _connection; 
    private void CancelWork()//object state 
    { 
     bool success = false; 

     try 
     { 
      if (_connection != null) 
      { 
       log.Debug("call cancel"); 
       _connection.Cancel(); 
       log.Debug("cancel complete"); 
       _connection.Close(); 
       log.Debug("close complete"); 
       success = true; 
       log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString()); 
      } 
     } 
     catch (Exception ex) 
     { 
      log.Error(ex.Message, ex); 
     } 

     SyncContextCancel.Send(CancelCompleted, new object[] { success }); 
    } 

    public void CancelCompleted(object state) 
    { 
     object[] args = (object[])state; 
     bool success = (bool)args[0]; 

     if (success) 
     { 
      log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString()); 

     } 
    } 
相關問題