如何在ActiveX方法调用期间防止WPF事件处理程序的重入?

时间:2021-02-03 00:00:16

We're calling methods on an ActiveX component from within a WPF and STA application. This calling is late-bound performed via:

我们从WPF和STA应用程序中调用ActiveX组件的方法。此调用是通过以下方式执行的:

res = ocx.GetType().InvokeMember(methodName, flags, null, ocx, args);

...where ocx is the ActiveX object retrieved with the System.Windows.Forms.AxHost.GetOcx() method.

…其中ocx是通过System.Windows.Forms.AxHost.GetOcx()方法检索的ActiveX对象。

This call is performed from within a WPF event handler, say 'mouse clicked'.

这个调用是在WPF事件处理程序中执行的,比如“鼠标单击”。

Now the problem. If we double-click the 'mouse clicked' event will trigger, running InvokeMember(). However, during this call, we see that the 'mouse clicked' event is re-entered. So in the same thread, we see the event handler twice on the call stack. This is very unexpected, and we're trying to prevent that. How can we prevent this from happening?

现在这个问题。如果双击“鼠标单击”事件将触发,运行InvokeMember()。然而,在此调用期间,我们看到“鼠标单击”事件被重新输入。在同一个线程中,我们在调用堆栈上看到两次事件处理程序。这是非常出乎意料的,我们正试图阻止它。我们如何防止这种情况发生?

The only reason we can think of why it happens is:

我们能想到它发生的唯一原因是:

  • The COM object is created in another STA, so we're performing a cross-STA call which needs to be marshalled
  • COM对象是在另一个STA中创建的,所以我们正在执行一个需要编组的cross STA调用
  • cross-thread STA calls uses a Windows Message to send an RPC request to the COM component
  • 跨线程STA调用使用Windows消息向COM组件发送一个RPC请求
  • cross-thread STA calls use the Windows Message pump to receive RPC answer
  • 跨线程STA调用使用Windows消息泵接收RPC应答
  • During the waiting another type of event comes in (like 'mouse clicked'), and this gets handled before the RPC answer gets handled.
  • 在等待期间,另一种类型的事件(如“鼠标单击”)进入,并且在RPC应答被处理之前对其进行处理。
  • This RPC answer gets handled
  • 这个RPC回答会被处理

Things we tried to fix the problem:

我们试图解决的问题:

  • use lock() in all event handlers. This does not work since lock() will lock a thread, and in this case it is the same thread which re-enters the event handler.
  • 在所有事件处理程序中使用lock()。这不起作用,因为lock()将锁定一个线程,在这种情况下,它是重新进入事件处理程序的同一线程。
  • use custom locking like 'bool locked = false; if (!locked) { locked = true; InvokeMethod(); ...; locked = false; }'. This works partially: it throws away the events instead of queuing them for later, and need an extensive change to all our event handlers, which is not nice to do.
  • 使用自定义锁定,如bool锁定= false;if (!locked) {locked = true;InvokeMethod();…;锁= false;}”。这在一定程度上是可行的:它丢弃事件而不是排队等待它们,并需要对所有事件处理程序进行大量更改,这不是一件好事。
  • use Dispatcher.DisableProcessing to stop (other) messages from being processed. This does not help: it throws an exception because of messages being processed anyways.
  • 使用调度程序。使(其他)消息停止处理的DisableProcessing。这并没有帮助:它抛出一个异常,因为消息正在被处理。
  • create a second dispatcher in a new thread, and run ocx.InvokeMehod() via Dispatcher.Invoke() to have it handled by another thread. This gives 'An event was unable to invoke any of the subscribers (Exception from HRESULT: 0x80040201)' (Yes, we're also subscribed to COM events of the ActiveX object).
  • 在一个新线程中创建第二个分派器,并通过dispatcher . invoke()运行ocx.InvokeMehod(),以便由另一个线程处理。这就产生了“一个事件无法调用任何订阅者(HRESULT: 0x80040201的异常)”(是的,我们还订阅了ActiveX对象的COM事件)。
  • use Dispatcher.PushFrame() to stop event handling from happening. This also fails.
  • 使用Dispatcher.PushFrame()来停止事件处理。这也失败。

A wild idea which might work, but don't know how to implement this would be creating a new message pump as the WPF message pump which can be configured to temporarily handle only RPC calls. This is along the lines of http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/430/WPF-MediaKit-Updates.aspx , but still somewhat different from this situation.

一个可能有效但不知道如何实现的疯狂想法是创建一个新的消息泵作为WPF消息泵,可以配置为仅临时处理RPC调用。这与http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/430/WPF-MediaKit-Updates.aspx类似,但与这种情况仍然有些不同。

So the question boils down to how can we make ActiveX call synchronously like we expected it already to be instead of async?

所以问题归结为我们如何让ActiveX调用像我们预期的那样同步而不是异步?

Update

更新

To make it more clear that the mechanism involved is not only about mouse-events, but the more generic problem of 'a new event is handled while the old is being executed', I'll give another example with a stack trace:

为了更清楚地说明所涉及的机制不仅仅是鼠标事件,而是“一个新事件被处理,而旧的正在被执行”的更一般的问题,我将再举一个堆栈跟踪的例子:

Context: we've got a WPF Grid on which we get a mouseclick (Grid_MouseDown), we've got an ActiveX object on which we perform the method 'CloseShelf'. Opening the shelf will take time, so we are subscribed to the event 'EventShelfClosed', which in the event handler of EventShelfClosed will call 'ListShelf' to know which shelfs are left.

上下文:我们有一个WPF网格,上面有一个mouseclick (Grid_MouseDown),我们有一个ActiveX对象,我们在该对象上执行方法“CloseShelf”。打开这个架子需要时间,所以我们订阅了事件“EventShelfClosed”,在EventShelfClosed的事件处理程序中,它将调用“ListShelf”来知道还剩下哪些架子。

This is how the managed stack trace looks like (Hans asked for an unmanaged stacktrace, but I don't know how to get one):

这就是托管堆栈跟踪的样子(Hans要求得到一个非托管堆栈跟踪,但我不知道如何获得它):

MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 53 C#
MyAxWrapper.dll!MyAxWrapper.LoggingMyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 151 + 0x14 bytes    C#
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMethod(string methodName, object[] args) Line 92 + 0x18 bytes C#
MyAxWrapper.dll!MyAxWrapper.MyAxAppWrapper.ListShelfs(string CanvasPageId) Line 300 + 0x42 bytes    C#
PACS.dll!PACS.MyAxDatabase.GetShelfIdsOn(string canvasPageId) Line 223 + 0xf bytes  C#
MyAxCanvas.dll!MyAxCanvas.MyAxCanvasPlugin.UpdateTimeLineSelection(string canvasPageId) Line 123 + 0x10 bytes   C#
MyAxCanvas.dll!MyAxCanvas.MyAxCanvasPlugin.EventShelfClosed(string canvasPageId, string shelfId) Line 180 + 0xb bytes   C#
MyAxWrapper.dll!MyAxWrapper.MyAxAppWrapper.FireEvent(string eventName, object[] args) Line 21 + 0x73 bytes  C#
MyAxWrapper.dll!MyAxWrapper.MyAxEventForwarder.EventShelfClosed(string CanvasPageID, string ShelfID) Line 177 + 0x58 bytes  C#
[Native to Managed Transition]  
[Native to Managed Transition]  
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 75 + 0x2b bytes    C#
MyAxWrapper.dll!MyAxWrapper.LoggingMyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 151 + 0x14 bytes    C#
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMethod(string methodName, object[] args) Line 92 + 0x18 bytes C#
MyAxWrapper.dll!MyAxWrapper.MyAxAppWrapper.CloseShelf(string a) Line 218 + 0x42 bytes   C#
MyAxCanvas.dll!MyAxCanvas.MyAxCanvasPlugin.EventCanvasPageCreated.AnonymousMethod__0(DataModel.Item exam) Line 110 + 0x1d bytes C#
ItemPresenter.dll!ItemPresenter.ItemPresenter.OnItemClicked(DataModel.Item study) Line 36 + 0x14 bytes  C#
ItemPresenter.dll!ItemPresenter.ItemPresenter.ItemPresenterPerYearControls_Click(object sender, System.Windows.RoutedEventArgs e) Line 215 + 0x1e bytes C#
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x78 bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0x1ae bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs e) + 0x17 bytes 
ItemPresenter.dll!ItemPresenter.ItemPresenterControl.Grid_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) Line 47 + 0x29 bytes    C#
PresentationCore.dll!System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) + 0x31 bytes    
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) + 0x29 bytes  
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x3e bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0x1ae bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent(System.Windows.RoutedEventArgs args) + 0x41 bytes   
PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs args, bool trusted) + 0x2c bytes    
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea() + 0x1ff bytes   
PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput(System.Windows.Input.InputEventArgs input) + 0x45 bytes 
PresentationCore.dll!System.Windows.Input.InputProviderSite.ReportInput(System.Windows.Input.InputReport inputReport) + 0x62 bytes  
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.ReportInput(System.IntPtr hwnd, System.Windows.Input.InputMode mode, int timestamp, System.Windows.Input.RawMouseActions actions, int x, int y, int wheel) + 0x263 bytes 
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.FilterMessage(System.IntPtr hwnd, MS.Internal.Interop.WindowMessage msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x46d bytes 
PresentationCore.dll!System.Windows.Interop.HwndSource.InputFilterMessage(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x75 bytes   
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0xbe bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x7d bytes    
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes 
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
WindowsBase.dll!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0xb4 bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x104 bytes    
[Native to Managed Transition]  
[Managed to Native Transition]  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0xc1 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes  
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x17 bytes  
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 
MyAxCanvasStandalone.exe!MyAxCanvasStandalone.App.Main(string[] args) Line 37 + 0xa bytes   C#
[Native to Managed Transition]  
[Managed to Native Transition]  
mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args) + 0x6d bytes    
Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() + 0x2a bytes  
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x63 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0xb0 bytes    
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes   
[Native to Managed Transition]  

What happens is the the method 'CloseShelf' will close the shelf, but in this case 'CloseShelf' is so fast that the event 'EventShelfClosed' is emitted and handled during the call to CloseShelf. Now CloseShelf will call ListShelfs, but ListShelfs will fail and return null because the ActiveX component is locked by the 'CloseShelf' call which is still active.

发生的情况是,方法“CloseShelf”将关闭架子,但在这种情况下,“CloseShelf”非常快,以至于在调用CloseShelf时发出并处理事件“EventShelfClosed”。现在,CloseShelf将调用listshelf,但ListShelfs将失败并返回null,因为ActiveX组件被仍然处于活动状态的“CloseShelf”调用锁定。

Why is this a problem? Becasue the programmer does not expect a method call to be async. This hit us after creating a large program, which now means auditting all calls for unexpected behaviour.

为什么这是个问题?因为程序员不希望方法调用是异步的。在创建了一个大型的程序之后,我们受到了打击,这意味着要对所有要求进行意外行为审计。

What would we like to see in this case? We would like to see that 'CloseShelf' returns without handling other events during the call. The method should be synchronous, and any pending events handled during the main (non-recursive) message loop.

在这种情况下,我们希望看到什么?我们希望看到“CloseShelf”在调用期间不处理其他事件。方法应该是同步的,并且在主(非递归)消息循环期间处理的任何挂起事件。

Having a 'lock' kind of boolean won't help here, since we would be missing events here, which locks up the application.

在这里使用“lock”类型的布尔值不会有什么帮助,因为我们会在这里丢失事件,从而锁住应用程序。

5 个解决方案

#1


2  

<tldr> try the code below in Attempt #3. There was nothing on TV when I sat down to write this.</tldr>

在尝试#3中尝试下面的代码。当我坐下来写这篇文章时,电视上什么都没有

Thanks for the clarification! I see that there is only one thread here; and also, since the CloseShelf frame is still on the stack, it looks like the COM call is actually blocking.

谢谢你的澄清!我看到这里只有一条线;而且,由于CloseShelf框架仍然在堆栈上,所以看起来COM调用实际上是阻塞的。

From the stack trace, it looks like the com object is calling GetMessage or PeekMessage API (or if it's VB6, DoEvents or similar) which will check the message queue and PROCESS messages on it - irregaurdless of if it will cause re-entrancy. AKA 'pumping the message queue' - but if it uses peekmessage, it won't block if there are no messages, but will still execute the messages. In your stacktrack these calls could be hidden in the invisible native part. The stack trace actually proves that somehow, the COM call is calling back into your code! It looks like you are aware of this. If it's a bit foggy for some reader here are a couple links:

从堆栈跟踪来看,com对象似乎正在调用GetMessage或PeekMessage API(或者如果是VB6、DoEvents或类似的API),它将检查消息队列并在其上处理消息——不考虑是否会导致重新进入。也就是“抽取消息队列”——但是如果它使用peekmessage,如果没有消息,它不会阻塞,但仍然会执行消息。在您的stacktrack中,这些调用可以隐藏在不可见的本机部分中。堆栈跟踪实际上证明了在某种程度上,COM调用正在调用您的代码!看起来你已经意识到了这一点。如果对某些读者来说有点模糊,这里有几个链接:

http://discuss.joelonsoftware.com/default.asp?joel.3.456478.15
http://en.wikipedia.org/wiki/Event_loop

http://discuss.joelonsoftware.com/default.asp?joel.3.456478.15 http://en.wikipedia.org/wiki/Event_loop

Cooperative multitasking (one message loop for the whole OS like win3.0): http://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing

协作多任务处理(对整个操作系统的一个消息循环,比如win3.0): http://en.wikipedia.org/wiki/computer_一心多用#Cooperative_multitasking.2Ftime-sharing

The quote 'voluntarily ceded time...' is actually done by these calls. And it STILL happens all the time on the GUI thread of every windows app!

引用“自愿放弃时间……”实际上是通过这些电话来完成的。而且在每个windows应用的GUI线程上,它仍然一直在发生!

Since the actual call is in the COM, if you can't edit it, you still have to code around it. It is probably just this one method (hopefully).

由于实际调用在COM中,如果不能编辑它,您仍然需要在它周围编写代码。它可能就是这个方法(希望如此)。

Sometimes programs and components check the message loop on purpose, to times to allow things to respond, or repaint the screen. Just like this poster is trying to do:

有时程序和组件会故意检查消息循环,以允许事情响应,或者重新绘制屏幕。就像这张海报所做的:

Is there a function like PeekMessage that doesn't process messages?

有没有像PeekMessage这样的函数不处理消息?

The quote about 'The system may also process internal events' is buring you here.

“系统也可能处理内部事件”的引文吸引了你。

note where you say 'the programmer does not expect a method call to be async' - it isn't async, or the stack trace would look different. it's 'recursing' back into your code. As an old Win3.1 programmer this was the hell we dealt with for EVERY app in the system!

请注意,当您说“程序员不希望方法调用是异步的”时,它不是异步的,或者堆栈跟踪看起来会有所不同。它会“递归”到你的代码中。作为一个老的Win3.1程序员,这就是我们处理系统中每个应用的地狱!

I found that link googling for 'check the message queue peekmessage' while trying to see if Get/Peekmessage, etc. could be prevented from processing messages. You can see from this documention...

我发现,在试图查看Get/ peekmessage等信息时,google搜索“检查消息队列peekmessage”的链接是否可以被阻止处理消息。你可以从这份文件中看到……

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943(v = vs.85). aspx

...that sending something like PM_QS_INPUT | PM_QS_PAINT would prevent the problem. Unfortunately, since you aren't calling it, you can't change it! And I didn't see any way to set a flag to control subsequent message processing from later calls.

…发送诸如PM_QS_INPUT | PM_QS_PAINT之类的东西可以防止这个问题。不幸的是,由于您没有调用它,您无法更改它!我没有看到任何设置标志来控制后续消息处理的方法。

If a reader is still confused (if not skip this code) ... For a simple example, make a VB Winforms App and make one button and double click it and paste this code in - (I say VB because application.doevents is the handiest way to invoke this nasty message queue check):

如果读者仍然感到困惑(如果不跳过这段代码)……对于一个简单的例子,制作一个VB Winforms应用程序,做一个按钮,双击并粘贴这个代码——(我说VB是因为应用程序。doevents是调用这个讨厌的消息队列检查的最方便的方式):

    For i As Integer = 0 To 20
        Text = i.ToString
        System.Threading.Thread.Sleep(100)
        Application.DoEvents()
    Next

Now click the button. Note you can enlarge the window and the background repaints - as the doevents allows these events to happen by checking the message queue (REM out the doevents and it will 'wait' untill the count is done; also try clicking 3x and you will get 3 counts in a row).

现在单击按钮。注意,您可以放大窗口和背景重绘——因为doevents允许通过检查消息队列来发生这些事件(REM out the doevents,它将“等待”直到计数完成;也可以尝试点击3x,你将获得连续3次计数)。

NOW... the kicker. Click the button w/ Doevents NOT commented out. Click the button 3 times - the countdowns interrupt each other, then resume as the previous one finishes. You can Pause the IDE and see 3 click callstacks.

现在…爱发牢骚的人。单击未注释的w/ Doevents按钮。点击按钮3次-计数中断相互中断,然后在上一个结束时继续。您可以暂停IDE并查看3单击callstack。

Yummy!

好吃!

I also see that you tried a couple things to stop messages or events from being processed. If the code that fires the EventShelfClosed is getting called from the re-entrancy caused by a PeekMessage or 'Doevents', however, that may not effect it.

我还看到您尝试了一些方法来阻止消息或事件被处理。然而,如果触发EventShelfClosed的代码正在从由PeekMessage或“Doevents”引起的重入中调用,则可能不会对其产生影响。

Note this practice has it's detractors: http://www.codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html

请注意,这种做法也有批评者:http://www.codinghorror.com/blog/2005/08/is-doevents-evil- revisi.html

Best would be to change the COM so it doesn't make any API calls that are checking the message loop.

最好是更改COM,这样它就不会进行任何检查消息循环的API调用。

Good luck with that!

祝你好运吧!

Another way to change it would be to move from events controlling EventShelfClosed, and call it explicity after the call to CloseShelf is exited (since the com call is really happening). Unfortunately your program's architechture probably won't allow that without major changes and/or increased cohesion and other stuff that dirties pretty models (not fashion models mind you :-).

另一种更改它的方法是从控制EventShelfClosed的事件转移,并在调用CloseShelf之后调用它显式(因为com调用确实正在发生)。不幸的是,如果没有重大的变化和/或增强的内聚性和其他会玷污漂亮模特的东西(不是时尚模特会提醒你:-),你的程序的架构可能不会允许你这样做。

A different way would be to make a new thread object pointed to a function that makes the com invoke, then start it, then join it, in hopes that anything like PeekMessage would not find a message pump on the new thread and therefore not interfere with things. It looks like a couple of you attempts involved this type of thing. Unfortunately if the COM Peeks for messages, and there is no message pump on the thread, kaboom. It will probably blow up instead of just ignoring things. Sounds like that's what happened. Further more, if the COM relies on other items that should only be accessed from the GUI/messagepump thread, you're in trouble with cross-thread calls (which would certainly be the case if the COM interacts with the UI or any UI objects).

另一种方法是让一个新的线程对象指向一个使com调用的函数,然后启动它,然后加入它,希望像PeekMessage那样的任何东西都不会在新线程上找到消息流,因此不会影响到事情。看起来你们中的一些人参与了这类事情。不幸的是,如果COM查找消息,并且线程上没有消息泵,kaboom。它可能会爆炸,而不仅仅是无视事物。听起来就是这样。此外,如果COM依赖于仅可以从GUI/messagepump线程访问的其他项,则会遇到跨线程调用的麻烦(如果COM与UI或任何UI对象交互,那么肯定会出现这种情况)。

If you can't stop the message queue from being checked, or prevent EventShelfClosed from firing until later, you have no choice but to let the EventShelfClosed get called. But what you can do is cause it to wait, and then fall thru when the CloseShelf is finished.

如果您无法阻止消息队列被检查,或者阻止EventShelfClosed直到稍后才能启动,那么您只能让EventShelfClosed被调用。但是你所能做的是让它等待,然后在CloseShelf完成后通过。

So you still have to have a class-level boolean field set/unset by CloseShelf so EventShelfClosed will know that it is running.

所以您仍然需要一个类级的布尔字段集/由CloseShelf取消设置,因此EventShelfClosed将知道它正在运行。

Unfortunately just checking this in a while loop, even with a sleep, will block the single thread you have, and freeze the app. You may just try to have EventShelfClosed re-raise itself and exit the function as long as the bool is set; but since RaiseEvent stays inside of managed, runs the code immediately, and does not check the message queue it will in crash with a *. If you can figure out how to re-raise EventShelfClosed by calling the PostMessage API (not SendMessage, that runs it right away) - that will keep putting on the GUI thread's message queue as many times as the COM call will make windows check it. Unless said COM waits for the queue to be empty for some stupid reason - another lockup. Not recommending.

不幸的是,只是在while循环中检查一下,即使是在睡眠状态下,也会阻塞你仅有的一个线程,并冻结应用程序。但是,由于RaiseEvent保持在managedobject中,因此会立即运行代码,并且不会检查它将在*中崩溃的消息队列。如果您能够通过调用PostMessage API(而不是马上运行它的SendMessage)来找到重新唤醒EventShelfClosed的方法,那么它将在COM调用使windows检查它的同时,不断地在GUI线程的消息队列上放置多次。除非说COM为了某个愚蠢的原因等待队列为空——另一个锁。不推荐。

Soo... You can fight fire with fire. Here I am implementing another message-checking loop to allow your events to happen while you wait for the bool to clear.

秀……你可以用火来灭火。在这里,我正在实现另一个消息检查循环,以便在等待bool清除时允许事件发生。

Note this is only going to fix this one problem in this one case. Auditing all calls ... this isn't a magic bullet. My guess is there is none. Very messy and this is a total hack.

注意这只会解决这个问题。审核所有调用……这不是一颗神奇的子弹。我猜没有。很乱,这完全是一个技巧。

Attempt #3

It's not really attempt #3, it's more like possibility #8. But I referenced this in my old answer and am too lazy to edit that.

它并不是真正的尝试3,它更像是可能性8。但我在我的旧答案中引用了这一点,我懒得编辑它。

Boolean InCloseShelf
function CloseShelf(...)
    InCloseShelf=True;
    try
    {
         com call and all else
     }
     finally
         InCloseShelf=False

function EventShelfClosed(...
    while (InCloseShelf)
    {
         DoEvents
     }

Now of course there is no DoEvents in WPF, it's in winforms' 'application'. This blog has an implementation

当然WPF中没有DoEvents,它在winforms中是“应用”。这个博客有一个实现

http://dedjo.blogspot.com/2007/08/how-to-doevents-in-wpf.html

http://dedjo.blogspot.com/2007/08/how-to-doevents-in-wpf.html

void DoEvents(){ 
DispatcherFrame f = new DispatcherFrame(); 
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,  
(SendOrPostCallback)delegate(object arg) { 
    DispatcherFrame fr =  arg as DispatcherFrame; 
    fr.Continue=True; 
}, f); 
Dispatcher.PushFrame(frame); 
}

Untested, of course! Note this is from the correction in the comments:

未经考验的,当然!注意,这是来自评论中的更正:

static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame(true);
Dispatcher.CurrentDispatcher.BeginInvoke
(
DispatcherPriority.Background, 
(SendOrPostCallback) delegate(object arg)
{
var f = arg as DispatcherFrame; 
f.Continue = false;
}, 
frame
);
Dispatcher.PushFrame(frame);
} 

Or you could always reference WinForms and call Application.DoEvents.

也可以引用WinForms并调用Application.DoEvents。

I'm guessing you already know this, but you're in a bad spot right now. If this doesn't do it, Good luck! If you find a better way please update the post, because, well, evil hacks like this suck, but now you can see why people say to check the message queue sparingly!

我猜你已经知道了,但你现在处境很糟糕。如果这还不行,祝你好运!如果你找到更好的方法,请更新帖子,因为,好吧,像这样的坏黑客,但是现在你可以看到为什么人们说要谨慎地检查消息队列!

#2


1  

I have dealt with similar problems in the past although not from WPF.

我曾经处理过类似的问题,虽然不是来自WPF。

In a win32 application the recommended approach was to use IMessageFilter::MessagePending - this could be configured to say what types of messages were allowed to be handled when an outgoing STA call was already in progress. Here you had to be careful to ensure that any callbacks from your callee object were accepted and handled.

在win32应用程序中,推荐的方法是使用IMessageFilter: MessagePending—可以将其配置为在发出的STA调用已经在进行时允许处理哪些类型的消息。在这里,您必须小心确保您的callee对象的任何回调都被接受和处理。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms694352(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms694352(v = vs.85). aspx

In WPF this is not available. I think your approach to using another thread is the correct way to go.

在WPF中,这是不可用的。我认为你使用另一个线程的方法是正确的。

In principle you want your main thread to block on a child thread. The child thread can then make the outgoing COM call. You probably want to make the child thread an STA to avoid introducing other unknown issues. It is important that messages are pumped on the child thread and that any pointers or types are correctly marshalled as the child thread will be in a different COM apartment. Reentrancy is avoided because callbacks are the only thing that would attempt to message the thread that is pumping.

原则上,您希望主线程阻塞子线程。子线程然后可以发出COM调用。您可能希望让子线程成为STA,以避免引入其他未知的问题。重要的是消息被注入到子线程上,并且任何指针或类型都被正确地编组,因为子线程将位于不同的COM单元中。避免重入,因为回调是惟一尝试向正在抽取的线程发送消息的东西。

In WPF I believe that a Dispatcher should provide all the functionality that you need.

在WPF中,我认为Dispatcher应该提供您所需要的所有功能。

I'm not sure why your attempt to do this using a Dispatcher failed - it might be related to this known issue: http://support.microsoft.com/kb/926997

我不确定为什么您尝试使用Dispatcher失败—它可能与这个已知的问题有关:http://support.microsoft.com/kb/926997

#3


1  

You have the answer!

Is it just an old hack? No, it's not, it's standard operating procedure when any type of re-entrancy is involved. This has worked flawlessly for me in more cases than I can remember, from humble single panel VB3 crud popups to huge MVVM/DDD wannable enterprise management apps.

这只是一个老家伙吗?不,这不是,当涉及到任何类型的重入时,这是标准操作程序。在我记忆中,这在很多情况下都是完美的,从简单的VB3 crud弹出窗口到大型的MVVM/DDD企业管理应用。

It is what you mentioned: 'use custom locking like 'static bool locked = false; if (!locked) { locked = true; InvokeMethod(); ...; locked = false; }'.

这就是你提到的:“使用自定义锁,比如”静态bool locked = false;if (!locked) {locked = true;InvokeMethod();…;锁= false;}”。

EDIT

Note the comments from the OP. OK so this won't solve the problem! The 2nd event isn't a spurious click; it is a different event, critical to the proper operation of the system.

注意op.com的评论,好吧,这解决不了问题!第二个事件不是虚假的点击;这是一个不同的事件,对系统的正常运行至关重要。

Please see my next answer for a few more attempts. #3 is the ugliest but should work.

再试几次,请看我的下一个答案。第三条是最丑的,但应该行得通。

#4


0  

If you already have asynchronous behavior I'd try Jeffrey Richter's PowerThreading library. It has AsyncEnumerator to simplify asynchronous programming. And it also has locking primitive which can help you implementing your scenario. As far as I know this primitive differs from regular Monitor class in that way that it doesn't allow to reenter your code even in the same thread so it might help you. Unfortunately I haven't tried that primitive so can't add much to it.

如果您已经有异步行为,我将尝试Jeffrey Richter的PowerThreading库。它有异步处理器来简化异步编程。它还具有锁定原语,可以帮助您实现您的场景。据我所知,这个原语与常规的Monitor类不同之处在于,它不允许在相同的线程中重新输入代码,因此它可能会对您有所帮助。不幸的是,我没有尝试过这种原始的方法,所以不能添加太多。

Here is an article on this primitive: http://msdn.microsoft.com/en-us/magazine/cc163532.aspx

这里有一篇关于这个原语的文章:http://msdn.microsoft.com/en-us/magazine/cc163532.aspx

#5


0  

I know this is not a complete answer, but I will like to clarify how events work. This will help you to understand that re-entrance is not a problem and it is the way events work.

我知道这不是一个完整的答案,但我想澄清事件是如何发生的。这将帮助您理解重新进入不是一个问题,而是事件的工作方式。

  1. Envent handler calls are always synchronous, but it does not mean that there will not be any other events in the stack.
  2. Envent处理程序调用总是同步的,但这并不意味着堆栈中不会有任何其他事件。
  3. Let's assume, you click a button inside a list box and you manually raise a list changed event and somewhere some call actually gets called.
  4. 让我们假设,你在列表框中点击一个按钮,然后手动地增加一个列表更改事件,在某个地方调用实际调用。

Your typical event raising call looks like,

典型的事件提升调用是这样的,

.... OnClick(...)
{
   if(SelectionChanged!=null)
       SelectionChanged(...)
}

Note that the OnClick call is still on the stack, while SelectionChanged event is fired, OnClick will not go out of stack till SelectionChanged call is completed.

注意,OnClick调用仍然在堆栈上,在触发SelectionChanged事件时,OnClick在完成SelectionChanged调用之前不会退出堆栈。

What would we like to see in this case? We would like to see that 'CloseShelf' returns without handling other events during the call. The method should be synchronous, and any pending events handled during the main (non-recursive) message loop.

在这种情况下,我们希望看到什么?我们希望看到“CloseShelf”在调用期间不处理其他事件。方法应该是同步的,并且在主(非递归)消息循环期间处理的任何挂起事件。

How is this possible if you are raising events within CloseShelf.

如果在CloseShelf中引发事件,这怎么可能呢?

The only way you would want to do this is by queuing event handler as in,

你想要这样做的唯一方法是通过排队事件处理程序,

.... OnClick(...)
{
   Dispatcher.BeginInvoke(delegate(){
   if(SelectionChanged!=null)
       SelectionChanged(...)
   });
}

This will raise event after OnClick is finishe, in this case you will not see OnClick on stack while SelectionChanged is performed.

这将在OnClick is finishe之后引发事件,在这种情况下,当执行SelectionChanged时,您将看不到OnClick在堆栈上。

#1


2  

<tldr> try the code below in Attempt #3. There was nothing on TV when I sat down to write this.</tldr>

在尝试#3中尝试下面的代码。当我坐下来写这篇文章时,电视上什么都没有

Thanks for the clarification! I see that there is only one thread here; and also, since the CloseShelf frame is still on the stack, it looks like the COM call is actually blocking.

谢谢你的澄清!我看到这里只有一条线;而且,由于CloseShelf框架仍然在堆栈上,所以看起来COM调用实际上是阻塞的。

From the stack trace, it looks like the com object is calling GetMessage or PeekMessage API (or if it's VB6, DoEvents or similar) which will check the message queue and PROCESS messages on it - irregaurdless of if it will cause re-entrancy. AKA 'pumping the message queue' - but if it uses peekmessage, it won't block if there are no messages, but will still execute the messages. In your stacktrack these calls could be hidden in the invisible native part. The stack trace actually proves that somehow, the COM call is calling back into your code! It looks like you are aware of this. If it's a bit foggy for some reader here are a couple links:

从堆栈跟踪来看,com对象似乎正在调用GetMessage或PeekMessage API(或者如果是VB6、DoEvents或类似的API),它将检查消息队列并在其上处理消息——不考虑是否会导致重新进入。也就是“抽取消息队列”——但是如果它使用peekmessage,如果没有消息,它不会阻塞,但仍然会执行消息。在您的stacktrack中,这些调用可以隐藏在不可见的本机部分中。堆栈跟踪实际上证明了在某种程度上,COM调用正在调用您的代码!看起来你已经意识到了这一点。如果对某些读者来说有点模糊,这里有几个链接:

http://discuss.joelonsoftware.com/default.asp?joel.3.456478.15
http://en.wikipedia.org/wiki/Event_loop

http://discuss.joelonsoftware.com/default.asp?joel.3.456478.15 http://en.wikipedia.org/wiki/Event_loop

Cooperative multitasking (one message loop for the whole OS like win3.0): http://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing

协作多任务处理(对整个操作系统的一个消息循环,比如win3.0): http://en.wikipedia.org/wiki/computer_一心多用#Cooperative_multitasking.2Ftime-sharing

The quote 'voluntarily ceded time...' is actually done by these calls. And it STILL happens all the time on the GUI thread of every windows app!

引用“自愿放弃时间……”实际上是通过这些电话来完成的。而且在每个windows应用的GUI线程上,它仍然一直在发生!

Since the actual call is in the COM, if you can't edit it, you still have to code around it. It is probably just this one method (hopefully).

由于实际调用在COM中,如果不能编辑它,您仍然需要在它周围编写代码。它可能就是这个方法(希望如此)。

Sometimes programs and components check the message loop on purpose, to times to allow things to respond, or repaint the screen. Just like this poster is trying to do:

有时程序和组件会故意检查消息循环,以允许事情响应,或者重新绘制屏幕。就像这张海报所做的:

Is there a function like PeekMessage that doesn't process messages?

有没有像PeekMessage这样的函数不处理消息?

The quote about 'The system may also process internal events' is buring you here.

“系统也可能处理内部事件”的引文吸引了你。

note where you say 'the programmer does not expect a method call to be async' - it isn't async, or the stack trace would look different. it's 'recursing' back into your code. As an old Win3.1 programmer this was the hell we dealt with for EVERY app in the system!

请注意,当您说“程序员不希望方法调用是异步的”时,它不是异步的,或者堆栈跟踪看起来会有所不同。它会“递归”到你的代码中。作为一个老的Win3.1程序员,这就是我们处理系统中每个应用的地狱!

I found that link googling for 'check the message queue peekmessage' while trying to see if Get/Peekmessage, etc. could be prevented from processing messages. You can see from this documention...

我发现,在试图查看Get/ peekmessage等信息时,google搜索“检查消息队列peekmessage”的链接是否可以被阻止处理消息。你可以从这份文件中看到……

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943(v = vs.85). aspx

...that sending something like PM_QS_INPUT | PM_QS_PAINT would prevent the problem. Unfortunately, since you aren't calling it, you can't change it! And I didn't see any way to set a flag to control subsequent message processing from later calls.

…发送诸如PM_QS_INPUT | PM_QS_PAINT之类的东西可以防止这个问题。不幸的是,由于您没有调用它,您无法更改它!我没有看到任何设置标志来控制后续消息处理的方法。

If a reader is still confused (if not skip this code) ... For a simple example, make a VB Winforms App and make one button and double click it and paste this code in - (I say VB because application.doevents is the handiest way to invoke this nasty message queue check):

如果读者仍然感到困惑(如果不跳过这段代码)……对于一个简单的例子,制作一个VB Winforms应用程序,做一个按钮,双击并粘贴这个代码——(我说VB是因为应用程序。doevents是调用这个讨厌的消息队列检查的最方便的方式):

    For i As Integer = 0 To 20
        Text = i.ToString
        System.Threading.Thread.Sleep(100)
        Application.DoEvents()
    Next

Now click the button. Note you can enlarge the window and the background repaints - as the doevents allows these events to happen by checking the message queue (REM out the doevents and it will 'wait' untill the count is done; also try clicking 3x and you will get 3 counts in a row).

现在单击按钮。注意,您可以放大窗口和背景重绘——因为doevents允许通过检查消息队列来发生这些事件(REM out the doevents,它将“等待”直到计数完成;也可以尝试点击3x,你将获得连续3次计数)。

NOW... the kicker. Click the button w/ Doevents NOT commented out. Click the button 3 times - the countdowns interrupt each other, then resume as the previous one finishes. You can Pause the IDE and see 3 click callstacks.

现在…爱发牢骚的人。单击未注释的w/ Doevents按钮。点击按钮3次-计数中断相互中断,然后在上一个结束时继续。您可以暂停IDE并查看3单击callstack。

Yummy!

好吃!

I also see that you tried a couple things to stop messages or events from being processed. If the code that fires the EventShelfClosed is getting called from the re-entrancy caused by a PeekMessage or 'Doevents', however, that may not effect it.

我还看到您尝试了一些方法来阻止消息或事件被处理。然而,如果触发EventShelfClosed的代码正在从由PeekMessage或“Doevents”引起的重入中调用,则可能不会对其产生影响。

Note this practice has it's detractors: http://www.codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html

请注意,这种做法也有批评者:http://www.codinghorror.com/blog/2005/08/is-doevents-evil- revisi.html

Best would be to change the COM so it doesn't make any API calls that are checking the message loop.

最好是更改COM,这样它就不会进行任何检查消息循环的API调用。

Good luck with that!

祝你好运吧!

Another way to change it would be to move from events controlling EventShelfClosed, and call it explicity after the call to CloseShelf is exited (since the com call is really happening). Unfortunately your program's architechture probably won't allow that without major changes and/or increased cohesion and other stuff that dirties pretty models (not fashion models mind you :-).

另一种更改它的方法是从控制EventShelfClosed的事件转移,并在调用CloseShelf之后调用它显式(因为com调用确实正在发生)。不幸的是,如果没有重大的变化和/或增强的内聚性和其他会玷污漂亮模特的东西(不是时尚模特会提醒你:-),你的程序的架构可能不会允许你这样做。

A different way would be to make a new thread object pointed to a function that makes the com invoke, then start it, then join it, in hopes that anything like PeekMessage would not find a message pump on the new thread and therefore not interfere with things. It looks like a couple of you attempts involved this type of thing. Unfortunately if the COM Peeks for messages, and there is no message pump on the thread, kaboom. It will probably blow up instead of just ignoring things. Sounds like that's what happened. Further more, if the COM relies on other items that should only be accessed from the GUI/messagepump thread, you're in trouble with cross-thread calls (which would certainly be the case if the COM interacts with the UI or any UI objects).

另一种方法是让一个新的线程对象指向一个使com调用的函数,然后启动它,然后加入它,希望像PeekMessage那样的任何东西都不会在新线程上找到消息流,因此不会影响到事情。看起来你们中的一些人参与了这类事情。不幸的是,如果COM查找消息,并且线程上没有消息泵,kaboom。它可能会爆炸,而不仅仅是无视事物。听起来就是这样。此外,如果COM依赖于仅可以从GUI/messagepump线程访问的其他项,则会遇到跨线程调用的麻烦(如果COM与UI或任何UI对象交互,那么肯定会出现这种情况)。

If you can't stop the message queue from being checked, or prevent EventShelfClosed from firing until later, you have no choice but to let the EventShelfClosed get called. But what you can do is cause it to wait, and then fall thru when the CloseShelf is finished.

如果您无法阻止消息队列被检查,或者阻止EventShelfClosed直到稍后才能启动,那么您只能让EventShelfClosed被调用。但是你所能做的是让它等待,然后在CloseShelf完成后通过。

So you still have to have a class-level boolean field set/unset by CloseShelf so EventShelfClosed will know that it is running.

所以您仍然需要一个类级的布尔字段集/由CloseShelf取消设置,因此EventShelfClosed将知道它正在运行。

Unfortunately just checking this in a while loop, even with a sleep, will block the single thread you have, and freeze the app. You may just try to have EventShelfClosed re-raise itself and exit the function as long as the bool is set; but since RaiseEvent stays inside of managed, runs the code immediately, and does not check the message queue it will in crash with a *. If you can figure out how to re-raise EventShelfClosed by calling the PostMessage API (not SendMessage, that runs it right away) - that will keep putting on the GUI thread's message queue as many times as the COM call will make windows check it. Unless said COM waits for the queue to be empty for some stupid reason - another lockup. Not recommending.

不幸的是,只是在while循环中检查一下,即使是在睡眠状态下,也会阻塞你仅有的一个线程,并冻结应用程序。但是,由于RaiseEvent保持在managedobject中,因此会立即运行代码,并且不会检查它将在*中崩溃的消息队列。如果您能够通过调用PostMessage API(而不是马上运行它的SendMessage)来找到重新唤醒EventShelfClosed的方法,那么它将在COM调用使windows检查它的同时,不断地在GUI线程的消息队列上放置多次。除非说COM为了某个愚蠢的原因等待队列为空——另一个锁。不推荐。

Soo... You can fight fire with fire. Here I am implementing another message-checking loop to allow your events to happen while you wait for the bool to clear.

秀……你可以用火来灭火。在这里,我正在实现另一个消息检查循环,以便在等待bool清除时允许事件发生。

Note this is only going to fix this one problem in this one case. Auditing all calls ... this isn't a magic bullet. My guess is there is none. Very messy and this is a total hack.

注意这只会解决这个问题。审核所有调用……这不是一颗神奇的子弹。我猜没有。很乱,这完全是一个技巧。

Attempt #3

It's not really attempt #3, it's more like possibility #8. But I referenced this in my old answer and am too lazy to edit that.

它并不是真正的尝试3,它更像是可能性8。但我在我的旧答案中引用了这一点,我懒得编辑它。

Boolean InCloseShelf
function CloseShelf(...)
    InCloseShelf=True;
    try
    {
         com call and all else
     }
     finally
         InCloseShelf=False

function EventShelfClosed(...
    while (InCloseShelf)
    {
         DoEvents
     }

Now of course there is no DoEvents in WPF, it's in winforms' 'application'. This blog has an implementation

当然WPF中没有DoEvents,它在winforms中是“应用”。这个博客有一个实现

http://dedjo.blogspot.com/2007/08/how-to-doevents-in-wpf.html

http://dedjo.blogspot.com/2007/08/how-to-doevents-in-wpf.html

void DoEvents(){ 
DispatcherFrame f = new DispatcherFrame(); 
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,  
(SendOrPostCallback)delegate(object arg) { 
    DispatcherFrame fr =  arg as DispatcherFrame; 
    fr.Continue=True; 
}, f); 
Dispatcher.PushFrame(frame); 
}

Untested, of course! Note this is from the correction in the comments:

未经考验的,当然!注意,这是来自评论中的更正:

static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame(true);
Dispatcher.CurrentDispatcher.BeginInvoke
(
DispatcherPriority.Background, 
(SendOrPostCallback) delegate(object arg)
{
var f = arg as DispatcherFrame; 
f.Continue = false;
}, 
frame
);
Dispatcher.PushFrame(frame);
} 

Or you could always reference WinForms and call Application.DoEvents.

也可以引用WinForms并调用Application.DoEvents。

I'm guessing you already know this, but you're in a bad spot right now. If this doesn't do it, Good luck! If you find a better way please update the post, because, well, evil hacks like this suck, but now you can see why people say to check the message queue sparingly!

我猜你已经知道了,但你现在处境很糟糕。如果这还不行,祝你好运!如果你找到更好的方法,请更新帖子,因为,好吧,像这样的坏黑客,但是现在你可以看到为什么人们说要谨慎地检查消息队列!

#2


1  

I have dealt with similar problems in the past although not from WPF.

我曾经处理过类似的问题,虽然不是来自WPF。

In a win32 application the recommended approach was to use IMessageFilter::MessagePending - this could be configured to say what types of messages were allowed to be handled when an outgoing STA call was already in progress. Here you had to be careful to ensure that any callbacks from your callee object were accepted and handled.

在win32应用程序中,推荐的方法是使用IMessageFilter: MessagePending—可以将其配置为在发出的STA调用已经在进行时允许处理哪些类型的消息。在这里,您必须小心确保您的callee对象的任何回调都被接受和处理。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms694352(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms694352(v = vs.85). aspx

In WPF this is not available. I think your approach to using another thread is the correct way to go.

在WPF中,这是不可用的。我认为你使用另一个线程的方法是正确的。

In principle you want your main thread to block on a child thread. The child thread can then make the outgoing COM call. You probably want to make the child thread an STA to avoid introducing other unknown issues. It is important that messages are pumped on the child thread and that any pointers or types are correctly marshalled as the child thread will be in a different COM apartment. Reentrancy is avoided because callbacks are the only thing that would attempt to message the thread that is pumping.

原则上,您希望主线程阻塞子线程。子线程然后可以发出COM调用。您可能希望让子线程成为STA,以避免引入其他未知的问题。重要的是消息被注入到子线程上,并且任何指针或类型都被正确地编组,因为子线程将位于不同的COM单元中。避免重入,因为回调是惟一尝试向正在抽取的线程发送消息的东西。

In WPF I believe that a Dispatcher should provide all the functionality that you need.

在WPF中,我认为Dispatcher应该提供您所需要的所有功能。

I'm not sure why your attempt to do this using a Dispatcher failed - it might be related to this known issue: http://support.microsoft.com/kb/926997

我不确定为什么您尝试使用Dispatcher失败—它可能与这个已知的问题有关:http://support.microsoft.com/kb/926997

#3


1  

You have the answer!

Is it just an old hack? No, it's not, it's standard operating procedure when any type of re-entrancy is involved. This has worked flawlessly for me in more cases than I can remember, from humble single panel VB3 crud popups to huge MVVM/DDD wannable enterprise management apps.

这只是一个老家伙吗?不,这不是,当涉及到任何类型的重入时,这是标准操作程序。在我记忆中,这在很多情况下都是完美的,从简单的VB3 crud弹出窗口到大型的MVVM/DDD企业管理应用。

It is what you mentioned: 'use custom locking like 'static bool locked = false; if (!locked) { locked = true; InvokeMethod(); ...; locked = false; }'.

这就是你提到的:“使用自定义锁,比如”静态bool locked = false;if (!locked) {locked = true;InvokeMethod();…;锁= false;}”。

EDIT

Note the comments from the OP. OK so this won't solve the problem! The 2nd event isn't a spurious click; it is a different event, critical to the proper operation of the system.

注意op.com的评论,好吧,这解决不了问题!第二个事件不是虚假的点击;这是一个不同的事件,对系统的正常运行至关重要。

Please see my next answer for a few more attempts. #3 is the ugliest but should work.

再试几次,请看我的下一个答案。第三条是最丑的,但应该行得通。

#4


0  

If you already have asynchronous behavior I'd try Jeffrey Richter's PowerThreading library. It has AsyncEnumerator to simplify asynchronous programming. And it also has locking primitive which can help you implementing your scenario. As far as I know this primitive differs from regular Monitor class in that way that it doesn't allow to reenter your code even in the same thread so it might help you. Unfortunately I haven't tried that primitive so can't add much to it.

如果您已经有异步行为,我将尝试Jeffrey Richter的PowerThreading库。它有异步处理器来简化异步编程。它还具有锁定原语,可以帮助您实现您的场景。据我所知,这个原语与常规的Monitor类不同之处在于,它不允许在相同的线程中重新输入代码,因此它可能会对您有所帮助。不幸的是,我没有尝试过这种原始的方法,所以不能添加太多。

Here is an article on this primitive: http://msdn.microsoft.com/en-us/magazine/cc163532.aspx

这里有一篇关于这个原语的文章:http://msdn.microsoft.com/en-us/magazine/cc163532.aspx

#5


0  

I know this is not a complete answer, but I will like to clarify how events work. This will help you to understand that re-entrance is not a problem and it is the way events work.

我知道这不是一个完整的答案,但我想澄清事件是如何发生的。这将帮助您理解重新进入不是一个问题,而是事件的工作方式。

  1. Envent handler calls are always synchronous, but it does not mean that there will not be any other events in the stack.
  2. Envent处理程序调用总是同步的,但这并不意味着堆栈中不会有任何其他事件。
  3. Let's assume, you click a button inside a list box and you manually raise a list changed event and somewhere some call actually gets called.
  4. 让我们假设,你在列表框中点击一个按钮,然后手动地增加一个列表更改事件,在某个地方调用实际调用。

Your typical event raising call looks like,

典型的事件提升调用是这样的,

.... OnClick(...)
{
   if(SelectionChanged!=null)
       SelectionChanged(...)
}

Note that the OnClick call is still on the stack, while SelectionChanged event is fired, OnClick will not go out of stack till SelectionChanged call is completed.

注意,OnClick调用仍然在堆栈上,在触发SelectionChanged事件时,OnClick在完成SelectionChanged调用之前不会退出堆栈。

What would we like to see in this case? We would like to see that 'CloseShelf' returns without handling other events during the call. The method should be synchronous, and any pending events handled during the main (non-recursive) message loop.

在这种情况下,我们希望看到什么?我们希望看到“CloseShelf”在调用期间不处理其他事件。方法应该是同步的,并且在主(非递归)消息循环期间处理的任何挂起事件。

How is this possible if you are raising events within CloseShelf.

如果在CloseShelf中引发事件,这怎么可能呢?

The only way you would want to do this is by queuing event handler as in,

你想要这样做的唯一方法是通过排队事件处理程序,

.... OnClick(...)
{
   Dispatcher.BeginInvoke(delegate(){
   if(SelectionChanged!=null)
       SelectionChanged(...)
   });
}

This will raise event after OnClick is finishe, in this case you will not see OnClick on stack while SelectionChanged is performed.

这将在OnClick is finishe之后引发事件,在这种情况下,当执行SelectionChanged时,您将看不到OnClick在堆栈上。