UI线程中的异步组件触发事件
我在.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,但我不明白如何将它应用于我的情况。
好的;所以这里就是我结束了一些阅读后:
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)是迄今为止最好的。建议非常非常受欢迎。
也许我不理解这个问题,但在我看来,你可以传递一个对你的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");
}
难道这就是你要找的人?
如果组件必须总是由同一个线程中使用,你可以做这样的事情:
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签名提供一个方法,以将该委托在工作线程上排队。
这使得使用我的组件的用户圈中存在太多的复杂性。 – ComponentBuilder 2010-01-07 17:48:33
复杂性在哪里?在组件的构造函数中指定委托? – 2010-01-07 17:50:41
我希望我的组件透明地异步(所以最终用户不会注意到)。当最终用户必须通过一个委托没有(明显)的原因,我认为这不是'用户友好'。 虽然我似乎找到了我的解决方案;但我不确定它是否是“最好的”解决方案。 – ComponentBuilder 2010-01-07 18:18:55
我似乎找到了我的解决方案:
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);
}
}
我还在测试这一点,但它似乎很好地工作。
当您的组件未在UI线程上创建时,这种方法会发生什么? – 2010-01-07 17:48:00
我忘了粘贴我的最新编辑,它检查_currentcontext是否为空;这现在已经修复(并编辑)。 – ComponentBuilder 2010-01-07 17:55:39
我刚刚检查过,如果捕获组件的构造函数中的SynchronizationContext并且组件是在非UI线程中创建的,则您的eventDelegate将在ThreadPool上执行。 – 2010-01-07 17:55:53
不是真的;这是使用我的组件时复杂的方法。所有的操作都是引用组件并使用如下代码: //用IP,端口等初始化MyComponent然后: MyComponent.SendString(“This is cool”); MyComponent引发事件;不管它们是由GUI还是非GUI项目处理都无关紧要,我不希望用户负责检查InvokeRequired。 – ComponentBuilder 2010-01-07 17:45:08