2010-01-07 53 views
7

我在.Net 2.0中構建了一個非可視化組件。該組件使用異步套接字(BeginReceive,EndReceive等)。異步回調在由運行時創建的工作線程的上下文中調用。組件用戶不必擔心多線程(這是我想要的主要目標)UI線程中的異步組件觸發事件

組件用戶可以在任何線程中創建我的非可視組件(UI線程只是簡單的普通線程應用程序,更嚴重的應用程序可以在任意工作線程中創建組件)。組件觸發事件,如「SessionConnected」或「DataAvailable」。

問題:由於異步回調和其中引發的事件,事件處理程序在工作程序線程上下文中執行。我想使用一箇中間層,它強制事件處理程序在首先創建 組件的線程的上下文中執行。

示例代碼(從異常處理等剝離...)

/// <summary> 
    /// Occurs when the connection is ended 
    /// </summary> 
    /// <param name="ar">The IAsyncResult to read the information from</param> 
    private void EndConnect(IAsyncResult ar) 
    { 
     // pass connection status with event 
     this.Socket.EndConnect(ar); 

     this.Stream = new NetworkStream(this.Socket); 

     // -- FIRE CONNECTED EVENT HERE -- 

     // Setup Receive Callback 
     this.Receive(); 
    } 


    /// <summary> 
    /// Occurs when data receive is done; when 0 bytes were received we can assume the connection was closed so we should disconnect 
    /// </summary> 
    /// <param name="ar">The IAsyncResult that was used by BeginRead</param> 
    private void EndReceive(IAsyncResult ar) 
    { 
     int nBytes; 
     nBytes = this.Stream.EndRead(ar); 
     if (nBytes > 0) 
     { 
      // -- FIRE RECEIVED DATA EVENT HERE -- 

      // Setup next Receive Callback 
      if (this.Connected) 
       this.Receive(); 
     } 
     else 
     { 
      this.Disconnect(); 
     } 
    } 

由於異步性質的插座使用我的組件的所有應用程序與散落「如果(this.InvokeRequired){... 「而我所需要的就是用戶能夠無憂無慮地使用我的組件作爲一種插件。

因此,如何在不需要用戶檢查InvokeRequired的情況下提升事件(或者換句話說,我如何強制與首先發起事件的線程在同一線程中引發的事件)?

我讀過關於AsyncOperation,BackgroundWorkers,SynchronizingObjects,AsyncCallbacks和其他東西的東西,但這一切都讓我頭暈目眩。

我沒有想出這個,想必笨拙,「解決方案」,但它似乎在某些情況下會失敗(當我的組件從WinForms項目經由例如靜態類的稱呼)

/// <summary> 
    /// Raises an event, ensuring BeginInvoke is called for controls that require invoke 
    /// </summary> 
    /// <param name="eventDelegate"></param> 
    /// <param name="args"></param> 
    /// <remarks>http://www.eggheadcafe.com/articles/20060727.asp</remarks> 
    protected void RaiseEvent(Delegate eventDelegate, object[] args) 
    { 
     if (eventDelegate != null) 
     { 
      try 
      { 
       Control ed = eventDelegate.Target as Control; 
       if ((ed != null) && (ed.InvokeRequired)) 
        ed.Invoke(eventDelegate, args); 
       else 
        eventDelegate.DynamicInvoke(args); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.GetType()); 
       Console.WriteLine(ex.Message); 
       //Swallow 
      } 
     } 
    } 

任何幫助將不勝感激。提前致謝!

編輯: 根據this thread我最好的選擇是使用SyncrhonizationContext.Post,但我不明白如何將它應用於我的情況。

回答

2

好的;所以這裏就是我結束了一些閱讀後:

public class MyComponent { 
    private AsyncOperation _asyncOperation; 

    /// Constructor of my component: 
    MyComponent() { 
     _asyncOperation = AsyncOperationManager.CreateOperation(null); 
    } 

    /// <summary> 
    /// Raises an event, ensuring the correct context 
    /// </summary> 
    /// <param name="eventDelegate"></param> 
    /// <param name="args"></param> 
    protected void RaiseEvent(Delegate eventDelegate, object[] args) 
    { 
     if (eventDelegate != null) 
     { 
      _asyncOperation.Post(new System.Threading.SendOrPostCallback(
       delegate(object argobj) 
       { 
        eventDelegate.DynamicInvoke(argobj as object[]); 
       }), args); 
     } 
    } 
} 

張貼在這裏其他的解決辦法是種類的工作正在進行中。這裏發佈的解決方案似乎(根據MSDN)是迄今爲止最好的。建議非常非常受歡迎。

0

也許我不理解這個問題,但在我看來,你可以傳遞一個對你的Async狀態的自定義對象的引用。

我把下面的例子放在一起來說明;

首先我們有一個Callback對象。這有2個屬性 - 一個控制派遣行動和一個行動來調用;

public class Callback 
{ 
    public Control Control { get; set; } 
    public Action Method { get; set; } 
} 

然後,我有一個WinForms項目時代碼執行完畢後調用另一個線程(使用的BeginInvoke)一些隨機代碼,然後顯示一個消息框。

private void Form1_Load(object sender, EventArgs e) 
    { 
     Action<bool> act = (bool myBool) => 
      { 
       Thread.Sleep(5000); 
      }; 

     act.BeginInvoke(true, new AsyncCallback((IAsyncResult result) => 
     { 
      Callback c = result.AsyncState as Callback; 
      c.Control.Invoke(c.Method); 

     }), new Callback() 
     { 
      Control = this, 
      Method =() => { ShowMessageBox(); } 
     });    
    } 

的ShowMessageBox方法必須在UI線程上運行,看起來像:

private void ShowMessageBox() 
    { 
     MessageBox.Show("Testing"); 
    } 

難道這就是你要找的人?

+0

不是真的;這是使用我的組件時複雜的方法。所有的操作都是引用組件並使用如下代碼: //用IP,端口等初始化MyComponent然後: MyComponent.SendString(「This is cool」); MyComponent引發事件;不管它們是由GUI還是非GUI項目處理都無關緊要,我不希望用戶負責檢查InvokeRequired。 – ComponentBuilder 2010-01-07 17:45:08

0

如果組件必須總是由同一個線程中使用,你可以做這樣的事情:

public delegate void CallbackInvoker(Delegate method, params object[] args); 

public YourComponent(CallbackInvoker invoker) 
{ 
    m_invoker = invoker; 
} 

protected void RaiseEvent(Delegate eventDelegate, object[] args) 
{ 
    if (eventDelegate != null) 
    { 
     try 
     { 
      if (m_invoker != null) 
       m_invoker(eventDelegate, args); 
      else 
       eventDelegate.DynamicInvoke(args); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.GetType()); 
      Console.WriteLine(ex.Message); 
      //Swallow 
     } 
    } 
} 

然後,當你從一個表或其它控制你可以做這個實例化組件:

YourComponent c = new YourComponent(this.Invoke); 

要在非UI工作線程上對事件進行排隊,它必須具有某種工作排隊機制,然後您可以爲CallbackInvoker簽名提供一個方法,以將該委託在工作線程上排隊。

+0

這使得使用我的組件的用戶圈中存在太多的複雜性。 – ComponentBuilder 2010-01-07 17:48:33

+0

複雜性在哪裏?在組件的構造函數中指定委託? – 2010-01-07 17:50:41

+0

我希望我的組件透明地異步(所以最終用戶不會注意到)。當最終用戶必須通過一個委託沒有(明顯)的原因,我認爲這不是'用戶友好'。 雖然我似乎找到了我的解決方案;但我不確定它是否是「最好的」解決方案。 – ComponentBuilder 2010-01-07 18:18:55

1

我似乎找到了我的解決方案:

private SynchronizationContext _currentcontext 

    /// Constructor of my component: 
    MyComponent() { 
     _currentcontext = WindowsFormsSynchronizationContext.Current; 
     //...or...? 
     _currentcontext = SynchronizationContext.Current; 
    } 

    /// <summary> 
    /// Raises an event, ensuring the correct context 
    /// </summary> 
    /// <param name="eventDelegate"></param> 
    /// <param name="args"></param> 
    protected void RaiseEvent(Delegate eventDelegate, object[] args) 
    { 
     if (eventDelegate != null) 
     { 
      if (_currentcontext != null) 
       _currentcontext.Post(new System.Threading.SendOrPostCallback(
        delegate(object a) 
        { 
         eventDelegate.DynamicInvoke(a as object[]); 
        }), args); 
      else 
       eventDelegate.DynamicInvoke(args); 
     } 
    } 

我還在測試這一點,但它似乎很好地工作。

+0

當您的組件未在UI線程上創建時,這種方法會發生什麼? – 2010-01-07 17:48:00

+0

我忘了粘貼我的最新編輯,它檢查_currentcontext是否爲空;這現在已經修復(並編輯)。 – ComponentBuilder 2010-01-07 17:55:39

+0

我剛剛檢查過,如果捕獲組件的構造函數中的SynchronizationContext並且組件是在非UI線程中創建的,則您的eventDelegate將在ThreadPool上執行。 – 2010-01-07 17:55:53