2010-01-07 54 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