我正在.Net 2.0中构建一个非可视组件。该组件使用异步套接字(BeginReceive,EndReceive等)。异步回调在运行时创建的工作线程的上下文中调用。组件用户不必担心多线程(这是主要目标,我想要的)
组件用户可以在任何线程中创建我的非可视组件(UI线程只是简单应用程序的通用线程。更严重的应用程序可以在任意工作线程中创建组件)。组件触发事件,例如“SessionConnected”或“DataAvailable”。
问题:由于Async Callbacks和其中引发的事件,事件处理程序在工作线程上下文中执行。我想使用一个强制的中间层 要在创建的线程的上下文中执行的事件处理程序 首先是组件。
示例代码(从异常处理等中剥离......)
/// <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();
}
}
由于Async套接字的性质,使用我的组件的所有应用程序都充满了“If(this.InvokeRequired){...”,而我想要的只是用户可以无忧无虑地使用我的组件作为排序闯入。
那么我如何在不要求用户检查InvokeRequired的情况下提升事件(或者换句话说,如何强制在与首先发起事件的线程相同的线程中引发的事件)? / p>
我已经阅读过有关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
}
}
}
任何帮助将不胜感激。提前谢谢!
编辑: 根据{{3}},我最好的选择是使用SyncrhonizationContext.Post,但我看不出如何将它应用到我的情况。
答案 0 :(得分: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)是迄今为止最好的解决方案。建议非常非常欢迎。
答案 1 :(得分: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);
}
}
我还在测试它,但似乎工作正常。
答案 2 :(得分: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");
}
这是你在找什么?
答案 3 :(得分: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
}
}
}
然后,当您从Form或其他控件实例化组件时,可以执行以下操作:
YourComponent c = new YourComponent(this.Invoke);
要在非UI工作线程上对事件进行排队,它必须具有某种工作排队机制,然后您可以使用CallbackInvoker的签名方法将该委托排队到工作线程上。