Как предотвратить повторный вход обработчика событий WPF во время вызова метода ActiveX? - PullRequest
9 голосов
/ 29 ноября 2011

Мы вызываем методы для компонента ActiveX из приложений WPF и STA.Этот вызов выполняется с поздним связыванием с помощью:

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

... где ocx - это объект ActiveX, полученный с помощью метода System.Windows.Forms.AxHost.GetOcx ().

Thisвызов выполняется из обработчика событий WPF, скажем, «щелкнул мышью».

Теперь проблема.Если мы дважды щелкнем мышью, будет вызвано событие «щелкнул мышью», запустив InvokeMember ().Однако, во время этого вызова мы видим, что событие «щелкнул мышью» повторно введено.Таким образом, в одном и том же потоке мы дважды видим обработчик событий в стеке вызовов.Это очень неожиданно, и мы пытаемся это предотвратить.Как мы можем предотвратить это?

Единственная причина, по которой мы можем подумать о , почему это происходит:

  • COM-объект создан в другой STA,поэтому мы выполняем кросс-STA-вызов, который необходимо маршалировать
  • кросс-поток STA-вызовы использует сообщение Windows для отправки RPC-запроса на COM-компонент
  • кросс-поток STA-вызовыиспользуйте насос сообщений Windows для получения ответа RPC
  • Во время ожидания наступает событие другого типа (например, «щелчок мышью»), и оно обрабатывается до обработки ответа RPC.
  • ЭтоОбрабатывается ответ RPC

Вещи, которые мы пытались исправить:

  • использование lock () во всех обработчиках событий.Это не работает, так как lock () заблокирует поток, и в этом случае это тот же поток, который повторно входит в обработчик событий.
  • использовать пользовательскую блокировку, например 'bool locked = false;if (! locked) {locked = true;InvokeMethod ();...;заблокирован = ложь;}».Это работает частично: он выбрасывает события, а не ставит их в очередь на потом, и требует значительного изменения в всех наших обработчиках событий, что нехорошо делать.
  • использовать Dispatcher.DisableProcessingостановить (другие) сообщения от обработки.Это не помогает: оно генерирует исключение из-за обработки сообщений в любом случае.
  • создает второго диспетчера в новом потоке и запускает ocx.InvokeMehod () через Dispatcher.Invoke (), чтобы обработать его другойнить.Это дает «Событию не удалось вызвать ни одного из подписчиков (исключение из HRESULT: 0x80040201)» (да, мы также подписаны на события COM объекта ActiveX).
  • use Dispatcher.PushFrame ()чтобы остановить обработку событий.Это также не помогает.

Дикая идея, которая может сработать, но не знаю, как реализовать это, будет создание нового обработчика сообщений в качестве обработчика сообщений WPF, который можно настроить для временной обработки только RPCзвонки.Это похоже на http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/430/WPF-MediaKit-Updates.aspx, но все же несколько отличается от этой ситуации.

Таким образом, вопрос сводится к тому, как мы можем сделать вызов ActiveX синхронным, как мы ожидали, что он уже будет вместо асинхронного?

Обновление

Чтобы было более понятно, что задействованный механизм касается не только событий мыши, но и более общей проблемы 'нового события, которое обрабатывается во времястарый выполняется », я приведу еще один пример с трассировкой стека:

Контекст: у нас есть WPF Grid, на котором мы получаем щелчок мыши (Grid_MouseDown), у нас есть объект ActiveXна котором мы выполняем метод 'CloseShelf'.Открытие полки займет время, поэтому мы подписаны на событие EventShelfClosed, которое в обработчике события EventShelfClosed будет вызывать ListShelf, чтобы узнать, какие полки остались.

Вот как трассировка управляемого стекавыглядит (Ганс попросил неуправляемую трассировку стека, но я не знаю, как ее получить):

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]  

Что происходит, если метод CloseShelf закроет полку, но в этом случае CloseShelf настолько быстр, что событие EventShelfClosed генерируется и обрабатывается во время вызова CloseShelf. Теперь CloseShelf будет вызывать ListShelfs, но ListShelfs завершится с ошибкой и вернет null, поскольку компонент ActiveX заблокирован вызовом CloseShelf, который все еще активен.

Почему это проблема? Поскольку программист не ожидает, что вызов метода будет асинхронным. Это поразило нас после создания большой программы, что теперь означает аудит всех вызовов на неожиданное поведение.

Что бы мы хотели увидеть в этом случае? Мы хотели бы видеть, что CloseShelf возвращается без обработки других событий во время вызова. Метод должен быть синхронным, и любые ожидающие события обрабатываются в течение основного (нерекурсивного) цикла сообщений.

Наличие логического значения «lock» здесь не поможет, так как здесь будут отсутствовать события, которые блокируют приложение.

Ответы [ 5 ]

2 голосов
/ 03 декабря 2011

попробуйте код ниже в Попытка # 3.Когда я сел, чтобы написать это, по телевизору ничего не было.

Спасибо за разъяснения!Я вижу, что здесь есть только одна нить;а также, поскольку кадр CloseShelf все еще находится в стеке, похоже, что вызов COM фактически блокируется.

Из трассировки стека выглядит, как будто com-объект вызывает GetMessage или PeekMessage API (или, если это VB6, DoEvents или аналогичный), который будет проверять очередь сообщений и сообщения PROCESS на нем - независимо от того, будет ли этовызвать повторный вход.AKA «качает очередь сообщений» - но если она использует peekmessage, она не будет блокироваться, если нет сообщений, но все равно будет выполнять сообщения.В вашем стеке эти вызовы могут быть скрыты в невидимой нативной части.Трассировка стека фактически доказывает, что вызов COM возвращается в ваш код!Похоже, вы знаете об этом.Если для какого-то читателя немного туманно, вот пара ссылок:

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

Совместная многозадачность (один цикл сообщений для всей ОС, такой как win3.0): http://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing

Этим призывам на самом деле делается цитата «добровольно переданное время ...».И это все еще происходит все время в потоке GUI каждого приложения Windows!

Поскольку фактический вызов находится в COM, если вы не можете его отредактировать, вам все равно придется кодировать его.Вероятно, это только один из этих методов (надеюсь).

Иногда программы и компоненты специально проверяют цикл сообщений, чтобы дать время ответить или перекрасить экран.Точно так же, как этот плакат пытается это сделать:

Есть ли такая функция, как PeekMessage, которая не обрабатывает сообщения?

Цитата «Система может также обрабатывать внутренниеevents 'похоронит вас здесь.

обратите внимание, где вы говорите, что «программист не ожидает, что вызов метода будет асинхронным» - это не асинхронно, иначе трассировка стека будет выглядеть иначе.он «возвращается» в ваш код.Как старый программист Win3.1, это был ад, с которым мы имели дело с КАЖДЫМ приложением в системе!

Я нашел эту ссылку, которая гуглила «проверить peekmessage очереди сообщений», пытаясь проверить, есть ли Get / Peekmessage и т. Д.. может быть запрещено обрабатывать сообщения.Вы можете видеть из этого документа ...

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

... что отправка что-то вроде PM_QS_INPUT |PM_QS_PAINT предотвратит проблему.К сожалению, так как вы не звоните, вы не можете это изменить!И я не видел никакого способа установить флаг для управления последующей обработкой сообщений от более поздних вызовов.

Если читатель все еще в замешательстве (если не пропустить этот код) ... Для простого примера сделайтеVB Winforms App и сделать одну кнопку и дважды щелкните его и вставьте этот код в - (я говорю VB, потому что application.doevents является самым удобным способом вызвать эту грязную проверку очереди сообщений):

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

Теперь нажмитекнопка.Обратите внимание, что вы можете увеличить окно и перерисовать фон - так как doevents позволяет этим событиям происходить путем проверки очереди сообщений (удалите doevents, и он будет «ждать», пока счетчик не будет завершен; также попробуйте нажать 3x, и вы получите 3считается подряд).

СЕЙЧАС ... кикер.Нажмите на кнопку без комментариев.Нажмите кнопку 3 раза - обратные отсчеты прерывают друг друга, а затем возобновляются по завершении предыдущего.Вы можете приостановить IDE и увидеть три клика стека вызовов.

Вкуснятина!

Я также вижу, что вы пытались сделать несколько вещей, чтобы остановить обработку сообщений или событий.Если код, который запускает EventShelfClosed, вызывается из повторного входа, вызванного PeekMessage или «Doevents», это может не повлиять.

Обратите внимание, что у этой практики есть свои недоброжелатели: http://www.codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html

Лучше всего было бы изменить COM, чтобы он не выполнял никаких вызовов API, проверяющих цикл сообщений.

Удачи с этим!

Еще один способ изменить его - это перейти от событий, управляющих EventShelfClosed, и назвать его явным после завершения вызова CloseShelf (так как вызов com действительно происходит).К сожалению, архитектура вашей программы, вероятно, не допустит этого без серьезных изменений и / или повышенной сплоченности и других вещей, которые загрязняют симпатичных моделей (не модные модели, обратите внимание: -).

Другим способом было бы сделать новыйОбъект потока указывает на функцию, которая вызывает com, затем запускает его, затем присоединяется к нему, в надежде, что что-то вроде PeekMessage не найдет насос сообщений в новом потоке и, следовательно, не будет мешать вещам.Похоже, что некоторые из вас пытались задействовать этот тип вещей.К сожалению, если COM ищет сообщения, и нет сообщений в потоке, kaboom.Это, вероятно, взорвется вместо того, чтобы просто игнорировать вещи.Похоже, это то, что случилось.Более того, если COM полагается на другие элементы, к которым следует обращаться только из потока GUI / messagepump, у вас возникают проблемы с межпоточными вызовами (что, несомненно, будет иметь место, если COM взаимодействует с пользовательским интерфейсом или какими-либо объектами пользовательского интерфейса).).

Если вы не можете остановить проверку очереди сообщений или запретить запуск EventShelfClosed до позднего времени, у вас нет другого выбора, кроме как вызвать EventShelfClosed.Но то, что вы можете сделать, это заставить его ждать, а затем падать, когда CloseShelf закончен.

Таким образом, вы все равно должны иметь логическое поле уровня класса, установленное / не установленное CloseShelf, чтобы EventShelfClosed знал, что он работает.

К сожалению, просто проверяя это в цикле while, даже в режиме сна, вы заблокируете единственный поток, который у вас есть, и остановите приложение.Вы можете просто попытаться заставить EventShelfClosed повторно поднять себя и выйти из функции, пока установлен bool;но поскольку RaiseEvent остается внутри управляемого объекта, он сразу же запускает код и не проверяет очередь сообщений, в случае сбоя он переходит с помощью переполнения стека.Если вы можете выяснить, как повторно вызвать EventShelfClosed, вызвав API PostMessage (а не SendMessage, который запускает его сразу) - это будет продолжать помещать в очередь сообщений потока GUI столько раз, сколько вызов COM заставит окна проверять.Если не указано, что COM ожидает пустую очередь по какой-то глупой причине - очередная блокировка.Не рекомендую.

Так ... Вы можете бороться с огнем с помощью огня.Здесь я реализую еще один цикл проверки сообщений, чтобы ваши события происходили, пока вы ожидаете очистки bool.

Обратите внимание, что это только исправит эту проблему в этом одном случае.Аудит всех звонков ... это не волшебная пуля.Я думаю, что нет ни одного.Очень грязно, и это полный взлом.

Попытка # 3

Это на самом деле не попытка № 3, это больше похоже на возможность № 8.Но я сослался на это в своем старом ответе и мне лень это редактировать.

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

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

Теперь, конечно, в WPF нет DoEvents, он есть в "приложении" winforms.Этот блог имеет реализацию

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); 
}

Не проверено, конечно!Обратите внимание, что это исправление в комментариях:

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);
} 

Или вы всегда можете ссылаться на WinForms и вызывать Application.DoEvents.

Полагаю, вы уже это знаете, но выв плохом месте прямо сейчас.Если это не делает, удачи!Если вы найдете лучший способ, пожалуйста, обновите пост, потому что, злые хаки, такие как этот отстой, но теперь вы можете понять, почему люди говорят, чтобы проверять очередь сообщений экономно!

1 голос
/ 02 декабря 2011

Я имел дело с подобными проблемами в прошлом, но не из WPF.

В приложении win32 рекомендуемый подход состоял в том, чтобы использовать IMessageFilter :: MessagePending - это можно настроить, чтобы указать, какие типы сообщений разрешено обрабатывать, когда исходящий вызов STA уже выполнялся. Здесь вы должны быть осторожны, чтобы гарантировать, что любые обратные вызовы от вашего объекта вызываемого были приняты и обработаны.

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

В WPF это недоступно. Я думаю, что ваш подход к использованию другого потока - правильный путь.

В принципе, вы хотите, чтобы ваш основной поток блокировался в дочернем потоке. Затем дочерний поток может сделать исходящий вызов COM. Возможно, вы захотите сделать дочерний поток STA, чтобы избежать появления других неизвестных проблем. Важно, чтобы сообщения перекачивались в дочерний поток и чтобы любые указатели или типы правильно распределялись, поскольку дочерний поток будет находиться в другой квартире COM. Избегать повторного входа, потому что обратные вызовы - это единственная вещь, которая пыталась бы сообщить потоку, который качает.

В WPF я считаю, что Dispatcher должен предоставлять все необходимые вам функции.

Я не уверен, почему ваша попытка сделать это с помощью Dispatcher не удалась - это может быть связано с этой известной проблемой: http://support.microsoft.com/kb/926997

1 голос
/ 01 декабря 2011

У вас есть ответ!

Это просто старый взлом? Нет, это не так, это стандартная рабочая процедура, когда задействован любой тип повторного входа. Для меня это работало безупречно во многих случаях, от того, что я могу вспомнить, от скромных однопанельных всплывающих окон VB3 до огромных приложений для управления предприятием MVVM / DDD.

Это то, что вы упомянули: «использовать пользовательскую блокировку как» static bool locked = false; if (! locked) {locked = true; InvokeMethod (); ...; заблокирован = ложь; }».

EDIT

Обратите внимание на комментарии от ОП. Хорошо, так что это не решит проблему! Второе событие не является ложным щелчком; это другое событие, критическое для правильной работы системы.

Пожалуйста, смотрите мой следующий ответ для нескольких попыток. № 3 самый уродливый, но должен работать.

0 голосов
/ 03 декабря 2011

Я знаю, что это не полный ответ, но я хотел бы уточнить, как работают события.Это поможет вам понять, что повторный вход не является проблемой, и именно так работают события.

  1. Вызовы обработчика Envent всегда синхронны, но это не значит, что в стеке не будет никаких других событий.
  2. Предположим, вы нажимаете кнопку внутри поля спискаи вы вручную создаете событие с измененным списком, и где-то фактически вызывается какой-то вызов.

Ваш типичный вызов вызова события выглядит следующим образом:

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

Обратите внимание, что вызов OnClick все еще включенстек, пока происходит событие SelectionChanged, OnClick не выйдет из стека до завершения вызова SelectionChanged.

Что бы мы хотели видеть в этом случае?Мы хотели бы видеть, что CloseShelf возвращается без обработки других событий во время вызова.Метод должен быть синхронным, и все ожидающие события должны обрабатываться в течение основного (нерекурсивного) цикла сообщений.

Как это возможно, если вы вызываете события в CloseShelf.

единственный способ сделать это - поставить в очередь обработчик событий, например:

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

Это вызовет событие после завершения OnClick, в этом случае вы не увидите OnClick в стеке, пока выполняется SelectionChanged.

0 голосов
/ 02 декабря 2011

Если у вас уже есть асинхронное поведение, я бы попробовал библиотеку Джеффри Рихтера PowerThreading. Он имеет AsyncEnumerator для упрощения асинхронного программирования. И он также имеет блокирующий примитив, который может помочь вам реализовать ваш сценарий. Насколько я знаю, этот примитив отличается от обычного класса Monitor тем, что он не позволяет повторно вводить код даже в одном потоке, поэтому он может вам помочь. К сожалению, я не пробовал этот примитив, поэтому не могу добавить к нему много.

Вот статья об этом примитиве: http://msdn.microsoft.com/en-us/magazine/cc163532.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...