C# 在主UI线程的.NET中引发事件
我正在.NET中开发一个类库,其他开发人员最终将使用它。该库使用了一些辅助线程,这些线程触发状态事件,这些事件将导致WinForms/WPF应用程序中的某些UI控件更新 通常,对于每次更新,您都需要检查WinForms上的.InvokeRequired属性或等效的WPF属性,并在主UI线程上调用该属性进行更新。这可能会很快变老,让最终开发人员这么做会让人感觉不对劲,所以 我的库是否可以从主UI线程触发/调用事件/委托? 特别是C# 在主UI线程的.NET中引发事件,c#,multithreading,design-patterns,C#,Multithreading,Design Patterns,我正在.NET中开发一个类库,其他开发人员最终将使用它。该库使用了一些辅助线程,这些线程触发状态事件,这些事件将导致WinForms/WPF应用程序中的某些UI控件更新 通常,对于每次更新,您都需要检查WinForms上的.InvokeRequired属性或等效的WPF属性,并在主UI线程上调用该属性进行更新。这可能会很快变老,让最终开发人员这么做会让人感觉不对劲,所以 我的库是否可以从主UI线程触发/调用事件/委托? 特别是 我是否应该自动“检测”要使用的“主”线程 如果不是,我是否应该要求最
UseThisThreadForEvents()
方法,以便从该调用中获取目标线程您的库可以检查事件调用列表中每个委托的目标,如果目标是ISynchronizeInvoke,则封送对目标线程的调用:
private void RaiseEventOnUIThread(Delegate theEvent, object[] args)
{
foreach (Delegate d in theEvent.GetInvocationList())
{
ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke;
if (syncer == null)
{
d.DynamicInvoke(args);
}
else
{
syncer.BeginInvoke(d, args); // cleanup omitted
}
}
}
另一种使线程契约更加明确的方法是,要求库的客户端为希望您在其上引发事件的线程传入ISynchronizeInvoke或SynchronizationContext。与“秘密检查代理目标”方法相比,这为库的用户提供了更多的可见性和控制
关于你的第二个问题,我将把线程封送的东西放在你的OnXxx或用户代码调用可能导致引发事件的任何API中。你可以使用该类在WinForms或WPF中封送对UI线程的调用。你可以将主线程的调度程序存储在你的库中,使用它来检查您是否正在UI线程上运行,并在必要时通过它在UI线程上执行 提供了一个很好的介绍和示例来说明如何做到这一点 其要点如下:
private Dispatcher _uiDispatcher;
// Call from the main thread
public void UseThisThreadForEvents()
{
_uiDispatcher = Dispatcher.CurrentDispatcher;
}
// Some method of library that may be called on worker thread
public void MyMethod()
{
if (Dispatcher.CurrentDispatcher != _uiDispatcher)
{
_uiDispatcher.Invoke(delegate()
{
// UI thread code
});
}
else
{
// UI thread code
}
}
以下是itwolson的想法,它是一种扩展方法,对我来说非常有用:
/// <summary>Extension methods for EventHandler-type delegates.</summary>
public static class EventExtensions
{
/// <summary>Raises the event (on the UI thread if available).</summary>
/// <param name="multicastDelegate">The event to raise.</param>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An EventArgs that contains the event data.</param>
/// <returns>The return value of the event invocation or null if none.</returns>
public static object Raise(this MulticastDelegate multicastDelegate, object sender, EventArgs e)
{
object retVal = null;
MulticastDelegate threadSafeMulticastDelegate = multicastDelegate;
if (threadSafeMulticastDelegate != null)
{
foreach (Delegate d in threadSafeMulticastDelegate.GetInvocationList())
{
var synchronizeInvoke = d.Target as ISynchronizeInvoke;
if ((synchronizeInvoke != null) && synchronizeInvoke.InvokeRequired)
{
retVal = synchronizeInvoke.EndInvoke(synchronizeInvoke.BeginInvoke(d, new[] { sender, e }));
}
else
{
retVal = d.DynamicInvoke(new[] { sender, e });
}
}
}
return retVal;
}
}
我非常喜欢Mike Bouk的答案(+1),我把它合并到了我的代码库中。我担心,由于参数不匹配,如果调用的委托不是EventHandler委托,则他的DynamicInvoke调用将引发运行时异常。由于您处于后台线程中,我假设您可能希望异步调用UI方法,并且您不关心它是否完成 下面的我的版本只能与EventHandler委托一起使用,并且将忽略其调用列表中的其他委托。由于EventHandler委托不返回任何内容,因此我们不需要结果。这允许我在异步进程完成后通过在BeginInvoke调用中传递EventHandler来调用EndInvoke。调用将通过AsynchronousCallback返回IAsyncResult.AsynchState中的此EventHandler,此时调用EventHandler.EndInvoke
/// <summary>
/// Safely raises any EventHandler event asynchronously.
/// </summary>
/// <param name="sender">The object raising the event (usually this).</param>
/// <param name="e">The EventArgs for this event.</param>
public static void Raise(this MulticastDelegate thisEvent, object sender,
EventArgs e)
{
EventHandler uiMethod;
ISynchronizeInvoke target;
AsyncCallback callback = new AsyncCallback(EndAsynchronousEvent);
foreach (Delegate d in thisEvent.GetInvocationList())
{
uiMethod = d as EventHandler;
if (uiMethod != null)
{
target = d.Target as ISynchronizeInvoke;
if (target != null) target.BeginInvoke(uiMethod, new[] { sender, e });
else uiMethod.BeginInvoke(sender, e, callback, uiMethod);
}
}
}
private static void EndAsynchronousEvent(IAsyncResult result)
{
((EventHandler)result.AsyncState).EndInvoke(result);
}
我发现依赖作为EventHandler的方法并不总是有效,而且ISynchronizeInvoke也不适用于WPF。因此,我的尝试是这样的,它可能会帮助某些人:
public static class Extensions
{
// Extension method which marshals events back onto the main thread
public static void Raise(this MulticastDelegate multicast, object sender, EventArgs args)
{
foreach (Delegate del in multicast.GetInvocationList())
{
// Try for WPF first
DispatcherObject dispatcherTarget = del.Target as DispatcherObject;
if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
{
// WPF target which requires marshaling
dispatcherTarget.Dispatcher.BeginInvoke(del, sender, args);
}
else
{
// Maybe its WinForms?
ISynchronizeInvoke syncTarget = del.Target as ISynchronizeInvoke;
if (syncTarget != null && syncTarget.InvokeRequired)
{
// WinForms target which requires marshaling
syncTarget.BeginInvoke(del, new object[] { sender, args });
}
else
{
// Just do it.
del.DynamicInvoke(sender, args);
}
}
}
}
// Extension method which marshals actions back onto the main thread
public static void Raise<T>(this Action<T> action, T args)
{
// Try for WPF first
DispatcherObject dispatcherTarget = action.Target as DispatcherObject;
if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
{
// WPF target which requires marshaling
dispatcherTarget.Dispatcher.BeginInvoke(action, args);
}
else
{
// Maybe its WinForms?
ISynchronizeInvoke syncTarget = action.Target as ISynchronizeInvoke;
if (syncTarget != null && syncTarget.InvokeRequired)
{
// WinForms target which requires marshaling
syncTarget.BeginInvoke(action, new object[] { args });
}
else
{
// Just do it.
action.DynamicInvoke(args);
}
}
}
}
公共静态类扩展
{
//将事件封送回主线程的扩展方法
public static void Raise(此多播代理多播、对象发送方、事件args args)
{
foreach(多播中的委托del.GetInvocationList())
{
//先试试WPF
DispatcherObject dispatcherTarget=del.Target作为DispatcherObject;
if(dispatcherTarget!=null&&!dispatcherTarget.Dispatcher.CheckAccess())
{
//需要封送处理的WPF目标
dispatcherTarget.Dispatcher.BeginInvoke(del、sender、args);
}
其他的
{
//也许是WinForms?
ISynchronizeInvoke syncTarget=del.Target作为ISynchronizeInvoke;
if(syncTarget!=null&&syncTarget.invokererequired)
{
//需要封送处理的WinForms目标
syncTarget.BeginInvoke(del,新对象[]{sender,args});
}
其他的
{
//就这么做吧。
del.DynamicInvoke(发送方,args);
}
}
}
}
//将操作封送回主线程的扩展方法
公共静态无效提升(此操作,T参数)
{
//先试试WPF
DispatcherObject dispatcherTarget=作为DispatcherObject的action.Target;
if(dispatcherTarget!=null&&!dispatcherTarget.Dispatcher.CheckAccess())
{
//需要封送处理的WPF目标
dispatcherTarget.Dispatcher.BeginInvoke(操作,参数);
}
其他的
{
//也许是WinForms?
ISynchronizeInvoke syncTarget=操作。目标为ISynchronizeInvoke;
if(syncTarget!=null&&syncTarget.invokererequired)
{
//需要封送处理的WinForms目标
syncTarget.BeginInvoke(操作,新对象[]{args});
}
其他的
{
//就这么做吧。
动作.动态Voke(args);
}
}
}
}
我喜欢这些答案和示例,但按照标准,您编写的库都是错的。不要为了其他线程而将事件封送到其他线程,这一点很重要。让你的事件在哪里触发,在哪里处理。当该事件需要更改线程时,让最终开发人员在该时间点进行更改是很重要的。我知道这是一个旧线程,但鉴于它确实帮助我开始构建类似的线程,因此我想分享我的代码。乌辛
MyEventHandlerEvent.Raise(this, MyEventArgs);
public static class Extensions
{
// Extension method which marshals events back onto the main thread
public static void Raise(this MulticastDelegate multicast, object sender, EventArgs args)
{
foreach (Delegate del in multicast.GetInvocationList())
{
// Try for WPF first
DispatcherObject dispatcherTarget = del.Target as DispatcherObject;
if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
{
// WPF target which requires marshaling
dispatcherTarget.Dispatcher.BeginInvoke(del, sender, args);
}
else
{
// Maybe its WinForms?
ISynchronizeInvoke syncTarget = del.Target as ISynchronizeInvoke;
if (syncTarget != null && syncTarget.InvokeRequired)
{
// WinForms target which requires marshaling
syncTarget.BeginInvoke(del, new object[] { sender, args });
}
else
{
// Just do it.
del.DynamicInvoke(sender, args);
}
}
}
}
// Extension method which marshals actions back onto the main thread
public static void Raise<T>(this Action<T> action, T args)
{
// Try for WPF first
DispatcherObject dispatcherTarget = action.Target as DispatcherObject;
if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
{
// WPF target which requires marshaling
dispatcherTarget.Dispatcher.BeginInvoke(action, args);
}
else
{
// Maybe its WinForms?
ISynchronizeInvoke syncTarget = action.Target as ISynchronizeInvoke;
if (syncTarget != null && syncTarget.InvokeRequired)
{
// WinForms target which requires marshaling
syncTarget.BeginInvoke(action, new object[] { args });
}
else
{
// Just do it.
action.DynamicInvoke(args);
}
}
}
}
public static void ThreadAwareRaise<TEventArgs>(this EventHandler<TEventArgs> customEvent,
object sender, TEventArgs e) where TEventArgs : EventArgs
{
foreach (var d in customEvent.GetInvocationList().OfType<EventHandler<TEventArgs>>())
switch (d.Target)
{
case DispatcherObject dispatchTartget:
dispatchTartget.Dispatcher.BeginInvoke(d, sender, e);
break;
case ISynchronizeInvoke syncTarget when syncTarget.InvokeRequired:
syncTarget.BeginInvoke(d, new[] {sender, e});
break;
default:
d.Invoke(sender, e);
break;
}
}