避免在跨线程WinForm事件处理中调用/ BeginInvoke的困境?
我仍然困扰于WinForm UI中的后台线程。 为什么? 以下是一些问题:
- 显然最重要的问题是,我不能修改一个控制,除非我在创build它的同一个线程上执行。
- 如你所知,Invoke,BeginInvoke等只有在创buildControl之后才可用。
- 即使在RequiresInvoke返回true之后,BeginInvoke仍然可以抛出ObjectDisposed,即使它不抛出,如果控件被销毁,它也不会执行代码。
- 即使在RequiresInvoke返回true之后,Invoke也可以无限期地挂起,等待与Invoke调用同时处理的控件执行。
我正在寻找一个优雅的解决scheme来解决这个问题,但在进入我所寻找的细节之前,我想我会澄清这个问题。 这是为了解决一般问题,并在后面加一个更具体的例子。 对于这个例子,假设我们通过互联网传输大量的数据。 用户界面必须能够显示正在进行的传输的进度对话框。 进度对话框应该不断快速更新(每秒更新5到20次)。 用户可以随时closures进度对话框并根据需要再次调用。 而且,假设参数为假,如果对话框是可见的,它必须处理每个进度事件。 用户可以在进度对话框中点击取消,并通过修改事件参数,取消操作。
现在我需要一个解决scheme,将适合下面的一些约束:
- 允许工作者线程调用Control / Form上的方法,并阻止/等待,直到执行完成。
- 允许对话本身在初始化或类似的地方调用这个相同的方法(因此不使用调用)。
- 处理方法或调用事件没有实施的负担,解决scheme应该只改变事件订阅本身。
- 适当地处理阻塞调用到可能正在处理的对话框中。 不幸的是,这不像检查IsDisposed那么简单。
- 必须能够与任何事件types一起使用(假定EventHandlertypes的委托)
- 不得将exception转换为TargetInvocationException。
- 该解决scheme必须使用.Net 2.0及更高版本
那么,鉴于上面的限制,这可以解决吗? 我搜遍了无数的博客和讨论,可惜我还是空手而归。
更新:我知道这个问题没有简单的答案。 我只在这个网站上呆了几天,而且我看到一些有很多经验的人回答了问题。 我希望这些人中的一个已经足够的解决了这个问题,我不用花一个星期左右的时间来构build一个合理的解决scheme。
更新#2:好吧,我将尝试更详细地描述问题,看看有什么(如果有的话)摆脱。 下面的属性可以让我们确定它的状态有一些事情引起关注…
-
Control.InvokeRequired =如果在当前线程上运行,或者IsHandleCreated为所有父项返回false,则logging为返回false。 InvokeRequired实现可能会抛出ObjectDisposedException,甚至可能重新创build对象的句柄,这让我很困扰。 而且,因为InvokeRequired在我们无法调用的时候(Dispose正在进行)可以返回true,即使我们可能需要使用invoke(Create in progress),它也可以返回false,所以这在任何情况下都是不可信的。 唯一的情况下,我可以看到我们可以信任的InvokeRequired返回false是当IsHandleCreated在调用之前和之后都返回true(BTW的MSDN文档为InvokeRequired提及检查IsHandleCreated)。
-
Control.IsHandleCreated =如果句柄已经分配给控件,则返回true; 否则,是错误的。 虽然IsHandleCreated是一个安全的调用,但如果控件正在重新创build它的句柄,它可能会崩溃。 这个潜在的问题似乎可以通过在访问IsHandleCreated和InvokeRequired时执行locking(控制)来解决。
-
Control.Disposing =如果控件正在处理过程中,则返回true。
- Control.IsDisposed =如果控件已被处置,则返回true。 我正在考虑订阅Disposed事件,并检查IsDisposed属性来确定是否BeginInvoke将完成。 这里最大的问题是在Disposing – > Disposed过渡期间没有同步锁。 如果您订阅了Disposed事件,那么validationDisposing == false && IsDisposed == false是可能的,您仍然可能看不到Disposed事件。 这是因为Dispose的实现设置Disposing = false,然后设置Disposed = true。 这为您提供了一个oppertunity(无论小小)在处置控件上读取Disposing和IsDisposed为false。
…我的头痛(希望上面的信息将会对有这些麻烦的任何人的问题更清楚一点,我非常感激你的多余思考周期。
closures麻烦…以下是Control.DestroyHandle()方法的后半部分:
if (!this.RecreatingHandle && (this.threadCallbackList != null)) { lock (this.threadCallbackList) { Exception exception = new ObjectDisposedException(base.GetType().Name); while (this.threadCallbackList.Count > 0) { ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue(); entry.exception = exception; entry.Complete(); } } } if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0) { UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero); } else { this.window.DestroyHandle(); }
你会注意到ObjectDisposedException被分派给所有正在等待的跨线程调用。 紧接着这是对this.window.DestroyHandle()的调用,这反过来破坏了窗口,并设置了对IntPtr.Zero的句柄引用,从而防止进一步调用BeginInvoke方法(或者更确切地说MarshaledInvoke处理BeginInvoke和Invoke)。 这里的问题是,在threadCallbackList的锁释放之后,可以在控件的线程置零窗口句柄之前插入一个新的条目。 这似乎是我看到的情况,虽然很less,往往足以停止释放。
更新#4:
对不起,继续拖下去; 不过,我认为这是值得在这里logging。 我已经设法解决了上面的大部分问题,并且正在缩小可用的解决scheme。 我又碰到了一个我所关心的问题,但是到现在为止,还没有看到“野外”。
这个问题与编写Control.Handle属性的天才有关:
public IntPtr get_Handle() { if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired) { throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name })); } if (!this.IsHandleCreated) { this.CreateHandle(); } return this.HandleInternal; }
这本身并不是那么糟糕(不pipe我对get {}的修改有何意见); 但是,当与InvokeRequired属性或Invoke / BeginInvoke方法结合使用时,它是不好的。 这里是Invoke的基本stream程:
if( !this.IsHandleCreated ) throw; ... do more stuff PostMessage( this.Handle, ... );
这里的问题是,从另一个线程,我可以成功地通过第一个if语句,之后该句柄被控制线程销毁,从而导致Handle属性的获得重新创build我的线程上的窗口句柄。 这可能会导致在原始控件的线程上引发exception。 这一个真的让我难住,因为没有办法防范这一点。 如果他们只使用InternalHandle属性和IntPtr.Zero的结果进行testing,这不会是一个问题。
如上所述,您的场景整齐地适合BackgroundWorker
– 为什么不使用它呢? 您对解决scheme的要求过于笼统,而且不合理 – 我怀疑是否有任何解决scheme能够满足所有问题。
我碰到了这个问题,并提出了涉及同步上下文的解决scheme。 解决scheme是将一个扩展方法添加到SynchronizationContext,该方法将特定委托绑定到SynchronizationContext所绑定的线程。 它将生成一个新的委托,当被调用时将编组调用到appropraite线程,然后调用原始委托。 这使得代表的消费者几乎不可能在错误的背景下称呼它。
关于这个问题的博客文章:
好的,几天后我已经完成了一个解决scheme。 它解决了所有列出的限制和目标的初始职位。 用法简单直接:
myWorker.SomeEvent += new EventHandlerForControl<EventArgs>(this, myWorker_SomeEvent).EventHandler;
当工作者线程调用这个事件时,它将处理所需的对控制线程的调用。 它确保它不会无限期挂起,并且如果无法在控制线程上执行,将始终引发ObjectDisposedException。 我创build了类的其他派生,一个忽略错误,另一个直接调用委托,如果控制不可用。 似乎运作良好,完全通过了几个testing,重现上述问题。 只有一个问题,我不能防止违反上述约束#3的解决scheme。 这个问题是问题描述中的最后一个(更新#4),拿到句柄的线程问题。 这可能会导致原始的控制线程出现意外的行为,我经常看到InvalidOperationException()调用Dispose(),因为在我的线程上创build的过程中的句柄。 为了处理这个问题,我确保访问使用Control.Handle属性的函数。 这允许表单在调用基本实现之前重载DestroyHandle方法并locking。 如果这样做了,这个类应该完全是线程安全的(据我所知)。
public class Form : System.Windows.Forms.Form { protected override void DestroyHandle() { lock (this) base.DestroyHandle(); } }
您可能会注意到解决死锁的核心方面就是轮询循环。 最初,我通过处理Disposed和HandleDestroyed控件的事件,并使用多个等待句柄成功地解决了testing用例。 经过更仔细的审查后,我发现这些事件的订阅/取消订阅不是线程安全的。 因此,我select轮询IsHandleCreated而不是在线程的事件上创build不必要的争用,从而避免仍然产生死锁状态的可能性。
无论如何,这是我提出的解决scheme:
/// <summary> /// Provies a wrapper type around event handlers for a control that are safe to be /// used from events on another thread. If the control is not valid at the time the /// delegate is called an exception of type ObjectDisposedExcpetion will be raised. /// </summary> [System.Diagnostics.DebuggerNonUserCode] public class EventHandlerForControl<TEventArgs> where TEventArgs : EventArgs { /// <summary> The control who's thread we will use for the invoke </summary> protected readonly Control _control; /// <summary> The delegate to invoke on the control </summary> protected readonly EventHandler<TEventArgs> _delegate; /// <summary> /// Constructs an EventHandler for the specified method on the given control instance. /// </summary> public EventHandlerForControl(Control control, EventHandler<TEventArgs> handler) { if (control == null) throw new ArgumentNullException("control"); _control = control.TopLevelControl; if (handler == null) throw new ArgumentNullException("handler"); _delegate = handler; } /// <summary> /// Constructs an EventHandler for the specified delegate converting it to the expected /// EventHandler<TEventArgs> delegate type. /// </summary> public EventHandlerForControl(Control control, Delegate handler) { if (control == null) throw new ArgumentNullException("control"); _control = control.TopLevelControl; if (handler == null) throw new ArgumentNullException("handler"); //_delegate = handler.Convert<EventHandler<TEventArgs>>(); _delegate = handler as EventHandler<TEventArgs>; if (_delegate == null) { foreach (Delegate d in handler.GetInvocationList()) { _delegate = (EventHandler<TEventArgs>) Delegate.Combine(_delegate, Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), d.Target, d.Method, true) ); } } if (_delegate == null) throw new ArgumentNullException("_delegate"); } /// <summary> /// Used to handle the condition that a control's handle is not currently available. This /// can either be before construction or after being disposed. /// </summary> protected virtual void OnControlDisposed(object sender, TEventArgs args) { throw new ObjectDisposedException(_control.GetType().Name); } /// <summary> /// This object will allow an implicit cast to the EventHandler<T> type for easier use. /// </summary> public static implicit operator EventHandler<TEventArgs>(EventHandlerForControl<TEventArgs> instance) { return instance.EventHandler; } /// <summary> /// Handles the 'magic' of safely invoking the delegate on the control without producing /// a dead-lock. /// </summary> public void EventHandler(object sender, TEventArgs args) { bool requiresInvoke = false, hasHandle = false; try { lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle { if (true == (hasHandle = _control.IsHandleCreated)) { requiresInvoke = _control.InvokeRequired; // must remain true for InvokeRequired to be dependable hasHandle &= _control.IsHandleCreated; } } } catch (ObjectDisposedException) { requiresInvoke = hasHandle = false; } if (!requiresInvoke && hasHandle) // control is from the current thread { _delegate(sender, args); return; } else if (hasHandle) // control invoke *might* work { MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args); IAsyncResult result = null; try { lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle result = _control.BeginInvoke(invocation.Invoker); } catch (InvalidOperationException) { } try { if (result != null) { WaitHandle handle = result.AsyncWaitHandle; TimeSpan interval = TimeSpan.FromSeconds(1); bool complete = false; while (!complete && (invocation.MethodRunning || _control.IsHandleCreated)) { if (invocation.MethodRunning) complete = handle.WaitOne();//no need to continue polling once running else complete = handle.WaitOne(interval); } if (complete) { _control.EndInvoke(result); return; } } } catch (ObjectDisposedException ode) { if (ode.ObjectName != _control.GetType().Name) throw;// *likely* from some other source... } } OnControlDisposed(sender, args); } /// <summary> /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo() /// implementation that allows us to preserve the exception types that are thrown rather than doing /// a delegate.DynamicInvoke(); /// </summary> [System.Diagnostics.DebuggerNonUserCode] private class MethodInvokerImpl { readonly EventHandler<TEventArgs> _handler; readonly object _sender; readonly TEventArgs _args; private bool _received; public MethodInvokerImpl(EventHandler<TEventArgs> handler, object sender, TEventArgs args) { _received = false; _handler = handler; _sender = sender; _args = args; } public MethodInvoker Invoker { get { return this.Invoke; } } private void Invoke() { _received = true; _handler(_sender, _args); } public bool MethodRunning { get { return _received; } } } }
如果您在这里发现任何问题,请告诉我。
我不打算为您写一个详尽的解决scheme,满足您的所有要求,但我会提供透视。 总的来说,我认为你是在满足这些要求的情况下拍摄月球。
Invoke
/ BeginInvoke
体系结构通过向控件的UI线程发送Windows消息来简单地执行提供的委托,并且消息循环自身执行委托。 这个的具体工作是无关紧要的,但重要的是没有特别的理由,你必须使用这个架构与UI线程同步线程。 所有你需要的是一些其他的循环运行,如在一个Forms.Timer
或类似的东西,监控一个Queue
的委托执行,这样做。 这将是相当简单的实现自己的,但我不知道什么具体会得到你的Invoke
和BeginInvoke
不提供。
这个问题的第二部分并不是真正的答案,但是我将把它仅仅作为参考。
private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters); public static object SafeInvoke(this Control control, Delegate method, params object[] parameters) { if (control == null) throw new ArgumentNullException("control"); if (control.InvokeRequired) { IAsyncResult result = null; try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); } catch (InvalidOperationException) { /* This control has not been created or was already (more likely) closed. */ } if (result != null) return control.EndInvoke(result); } else { if (!control.IsDisposed) return method.DynamicInvoke(parameters); } return null; }
这段代码应该避免使用Invoke / BeginInvoke最常见的陷阱,而且易于使用。 转过来
if (control.InvokeRequired) control.Invoke(...) else ...
成
control.SafeInvoke(...)
类似的构造是可能的BeginInvoke。
哇,很长的问题。 我会试着组织我的答案,如果我明白了什么错误,你可以纠正我,好吗?
1)除非你有非常好的理由直接从不同的线程调用UI方法,否则不要。 你总是可以使用事件处理程序去制作者/消费者模型:
protected override void OnLoad() { //... component.Event += new EventHandler(myHandler); } protected override void OnClosing() { //... component.Event -= new EventHandler(myHandler); }
例如,每当不同线程中的组件需要在UI中执行某些操作时,myHandler就会被触发。 此外,在OnLoad中设置事件处理程序并在OnClosing中取消订阅可确保事件只在UI创build并处理事件时才由UI接收/处理。 如果正在处理的过程中,您甚至不能将事件触发到此对话框中,因为您将不再订阅该事件。 如果另外一个事件是在正在处理中的情况下被触发的,则它将被排队。
您可以在事件参数中传递所需的所有信息:是否更新进度,closures窗口等
2)如果您使用上面build议的模型,则不需要InvokeRequired。 在这个例子中,例如,你知道唯一触发myHandler的就是你的组件,它存在于另一个线程中。
private void myHandler(object sender, EventArgs args) { BeginInvoke(Action(myMethod)); }
所以你总是可以使用invoke来确定你将会在正确的线程中。
3)小心同步呼叫。 如果你想,你可以replace使用Invoke而不是BeginInvoke。 这将阻止你的组件,直到事件已经被处理。 但是,如果在UI中需要与组件所在线程独占的内容进行通信,则可能会出现死锁问题。 (我不知道我是否清楚,请让我知道)。 当使用reflection(TargetInvocationException)和BeginInvoke(因为它们启动不同的线程,你失去了堆栈跟踪的一部分)时,我遇到了exception问题,但是我不记得Invoke调用有很多麻烦,所以你应该在例外情况下保持安全。
哇,很长的回答。 如果有任何机会,我错过了你的任何要求或误解了你所说的话(英文不是我的母语,所以我们不能确定),请让我知道。
我尝试将所有这些调用消息组织到GUI中,如火灾和遗忘(处理GUI由于处理表单时的竞态条件而可能抛出的exception)。
这种方式如果从来没有执行任何伤害已经完成。
如果GUI需要响应工作线程,则有一种方法可以有效地反转通知。 为了简单的需要,BackgroundWorker已经处理了这个。
这是一个相当困难的问题。 正如我在评论中提到的,考虑到logging的限制条件,我不认为这是可以解决的。 你可以通过一个特定的.net框架实现来攻击它:知道各种成员函数的实现可能会帮助你通过获取锁来获得作弊,并且知道“实际上可以在另一个线程上调用其他成员函数。 “
所以,我现在的基本答案是“不”。 我不想说这是不可能的,因为我对.Net框架有很大的信心。 另外,我相对来说是一个新手,没有学过一般的框架,或CS,但互联网是开放的(即使像我这样的无知的人)!
在另一个话题上,这个论点可以被提出并得到很好的支持,“你永远不需要Invoke
,只能使用BeginInvoke
,而且会被BeginInvoke
。 我不打算试图支持它,甚至说这是一个正确的断言,但我会说,共同的实施是不正确的,并构成一个工作(我希望)。
这里有一个通用的实现(在这里从不同的答案):
protected override void OnLoad() { //... component.Event += new EventHandler(myHandler); } protected override void OnClosing() { //... component.Event -= new EventHandler(myHandler); }
这不是线程安全的。 组件在撤消订阅之前可以很容易地开始调用调用列表,并且只有在完成configuration之后才能调用处理程序。 真实的一点是,没有logging每个组件如何在.Net中使用事件机制,并且说实话,他根本不需要取消订阅你的电话号码:一旦你发出了你的电话号码,没有人需要删除它!
更好的是:
protected override void OnLoad(System.EventArgs e) { component.Event += new System.EventHandler(myHandler); } protected override void OnFormClosing(FormClosedEventArgs e) { component.Event -= new System.EventHandler(myHandler); lock (lockobj) { closing = true; } } private void Handler(object a, System.EventArgs e) { lock (lockobj) { if (closing) return; this.BeginInvoke(new System.Action(HandlerImpl)); } } /*Must be called only on GUI thread*/ private void HandlerImpl() { this.Hide(); } private readonly object lockobj = new object(); private volatile bool closing = false;
请让我知道,如果我错过了什么。
如果你不喜欢BackgroundWoker(如@Pavel所描述的),你可能想看看这个库http://www.wintellect.com/PowerThreading.aspx 。
如果我明白这一点,为什么你需要在应用程序运行时处理进度对话框? 为什么不显示和隐藏在用户的请求? 这听起来像会让你的问题至less有点简单。
为什么不在使用者解散时隐藏对话框呢? 如果你没有模态地显示那个对话框,这应该可以正常工作。 (使用show而不是showdialog)。 我相信当你调用show时,你可以通过将主机传递给对话框来保持你的进度对话框在你自己的窗口之上(如果你需要的话)。
使用System.ComponentModel.ISynchronizeInvoke
在创buildSystem.ComponentModel.Component
(如BackgroundWorker
时很好用。 以下代码片段是FileSystemWater
处理事件的方式。
''' <summary> ''' Gets or sets the object used to marshal the event handler calls issued as a result of finding a file in a search. ''' </summary> <IODescription(SR.FSS_SynchronizingObject), DefaultValue(CType(Nothing, String))> _ Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke Get If (_synchronizingObject Is Nothing) AndAlso (MyBase.DesignMode) Then Dim oHost As IDesignerHost = DirectCast(MyBase.GetService(GetType(IDesignerHost)), IDesignerHost) If (Not (oHost Is Nothing)) Then Dim oRootComponent As Object = oHost.RootComponent If (Not (oRootComponent Is Nothing)) AndAlso (TypeOf oRootComponent Is ISynchronizeInvoke) Then _synchronizingObject = DirectCast(oRootComponent, ISynchronizeInvoke) End If End If End If Return _synchronizingObject End Get Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke) _synchronizingObject = Value End Set End Property Private _onStartupHandler As EventHandler Protected Sub OnStartup(ByVal e As EventArgs) If ((Not Me.SynchronizingObject Is Nothing) AndAlso Me.SynchronizingObject.InvokeRequired) Then Me.SynchronizingObject.BeginInvoke(_onStartupHandler, New Object() {Me, e}) Else _onStartupHandler.Invoke(Me, e) End If End Sub
这是我目前使用的。 它基于SynchronizationContext的使用,受到JaredPar博客文章的启发 – 请参阅上面的答案。 这可能不是完美的,但它确实避免了我也遇到的一些OP的问题。
// Homemade Action-style delegates to provide .Net 2.0 compatibility, since .Net 2.0 does not // include a non-generic Action delegate nor Action delegates with more than one generic type // parameter. (The DMethodWithOneParameter<T> definition is not needed, could be Action<T> // instead, but is defined for consistency.) Some interesting observations can be found here: // http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx public delegate void DMethodWithNoParameters(); public delegate void DMethodWithOneParameter<T>(T parameter1); public delegate void DMethodWithTwoParameters<T1, T2>(T1 parameter1, T2 parameter2); public delegate void DMethodWithThreeParameters<T1, T2, T3>(T1 parameter1, T2 parameter2, T3 parameter3); /// <summary> /// Class containing support code to use the SynchronizationContext mechanism to dispatch the /// execution of a method to the WinForms UI thread, from another thread. This can be used as an /// alternative to the Control.BeginInvoke() mechanism which can be problematic under certain /// conditions. See for example the discussion here: /// http://stackoverflow.com/questions/1364116/avoiding-the-woes-of-invoke-begininvoke-in-cross-thread-winform-event-handling /// /// As currently coded this works with methods that take zero, one, two or three arguments, but /// it is a trivial job to extend the code for methods taking more arguments. /// </summary> public class WinFormsHelper { // An arbitrary WinForms control associated with thread 1, used to check that thread-switching // with the SynchronizationContext mechanism should be OK private readonly Control _thread1Control = null; // SynchronizationContext for the WinForms environment's UI thread private readonly WindowsFormsSynchronizationContext _synchronizationContext; /// <summary> /// Constructor. This must be called on the WinForms UI thread, typically thread 1. (Unless /// running under the Visual Studio debugger, then the thread number is arbitrary.) /// /// The provided "thread 1 control" must be some WinForms control that will remain in /// existence for as long as this object is going to be used, for example the main Form /// control for the application. /// </summary> /// <param name="thread1Control">see above</param> public WinFormsHelper(Control thread1Control) { _thread1Control = thread1Control; if (thread1Control.InvokeRequired) throw new Exception("Not called on thread associated with WinForms controls."); _synchronizationContext = SynchronizationContext.Current as WindowsFormsSynchronizationContext; if (_synchronizationContext == null) // Should not be possible? throw new Exception("SynchronizationContext.Current = null or wrong type."); } // The following BeginInvoke() methods follow a boilerplate pattern for how these methods // should be implemented - they differ only in the number of arguments that the caller wants // to provide. public void BeginInvoke(DMethodWithNoParameters methodWithNoParameters) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithNoParameters(); }, null); } public void BeginInvoke<T>(DMethodWithOneParameter<T> methodWithOneParameter, T parameter1) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithOneParameter(parameter1); }, null); } public void BeginInvoke<T1, T2>(DMethodWithTwoParameters<T1, T2> methodWithTwoParameters, T1 parameter1, T2 parameter2) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithTwoParameters(parameter1, parameter2); }, null); } public void BeginInvoke<T1, T2, T3>(DMethodWithThreeParameters<T1, T2, T3> methodWithThreeParameters, T1 parameter1, T2 parameter2, T3 parameter3) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithThreeParameters(parameter1, parameter2, parameter3); }, null); } }