Gui возвращается с управляемого ожидания - PullRequest
3 голосов
/ 06 сентября 2010

Я обнаружил проблему повторного входа при использовании NotifyIcons. Это действительно легко воспроизвести, просто поместите NotiftIcon на форму, и событие click должно выглядеть так:

private bool reentrancyDetected;
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
    if (reentrancyDetected) MessageBox.Show("Reentrancy");
    reentrancyDetected = true;
    lock (thisLock)
    {
        //do nothing
    }
    reentrancyDetected = false;
}

Также запустите фоновый поток, который вызовет некоторую конкуренцию:

private readonly object thisLock = new object();
private readonly Thread bgThread;
public Form1()
{
    InitializeComponent();
    bgThread = new Thread(BackgroundOp) { IsBackground = true };
    bgThread.Start();
}

private void BackgroundOp()
{
    while (true)
    {
        lock (thisLock)
        {
            Thread.Sleep(2000);
        }
    }
}

Теперь, если вы начнете нажимать на уведомление, появится сообщение с указанием повторного входа. Мне известны причины, по которым управляемое ожидание в STA должно качать сообщения для некоторых окон. Но я не уверен, почему сообщения notifyicon прокачаны. Также есть ли способ избежать перекачки без использования некоторых логических индикаторов при входе / выходе из методов?

Ответы [ 2 ]

5 голосов
/ 06 сентября 2010

Вы можете увидеть, что происходит, если вы замените вызов MessageBox.Show на Debugger.Break и подключите отладчик с включенной встроенной отладкой, когда перерыв сработает.Стек вызовов выглядит следующим образом:

WindowsFormsApplication3.exe!WindowsFormsApplication3.Form1.notifyIcon1_MouseClick(object sender = {System.Windows.Forms.NotifyIcon}, System.Windows.Forms.MouseEventArgs e = {X = 0x00000000 Y = 0x00000000 Button = Left}) Line 30 + 0x1e bytes   C#
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.OnMouseClick(System.Windows.Forms.MouseEventArgs mea) + 0x6d bytes 
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button) + 0x7e bytes   
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.WndProc(ref System.Windows.Forms.Message msg) + 0xb3 bytes 
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.NotifyIconNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0xc bytes 
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg = 0x00000800, System.IntPtr wparam, System.IntPtr lparam) + 0x5a bytes  
user32.dll!_InternalCallWinProc@20()  + 0x23 bytes  
user32.dll!_UserCallWinProcCheckWow@32()  + 0xb3 bytes  
user32.dll!_DispatchClientMessage@20()  + 0x4b bytes    
user32.dll!___fnDWORD@4()  + 0x24 bytes 
ntdll.dll!_KiUserCallbackDispatcher@12()  + 0x2e bytes  
user32.dll!_NtUserPeekMessage@20()  + 0xc bytes 
user32.dll!__PeekMessage@24()  + 0x2d bytes 
user32.dll!_PeekMessageW@20()  + 0xf4 bytes 
ole32.dll!CCliModalLoop::MyPeekMessage()  + 0x30 bytes  
ole32.dll!CCliModalLoop::PeekRPCAndDDEMessage()  + 0x30 bytes   
ole32.dll!CCliModalLoop::FindMessage()  + 0x30 bytes    
ole32.dll!CCliModalLoop::HandleWakeForMsg()  + 0x41 bytes   
ole32.dll!CCliModalLoop::BlockFn()  - 0x5df7 bytes  
ole32.dll!_CoWaitForMultipleHandles@20()  - 0x51b9 bytes    
WindowsFormsApplication3.exe!WindowsFormsApplication3.Form1.notifyIcon1_MouseClick(object sender = {System.Windows.Forms.NotifyIcon}, System.Windows.Forms.MouseEventArgs e = {X = 0x00000000 Y = 0x00000000 Button = Left}) Line 32 + 0x14 bytes   C#

Соответствующей функцией является CoWaitForMultipleHandles.Это гарантирует, что поток STA не может блокировать синхронизируемый объект, не пересылая сообщения.Что очень вредно для здоровья, так как это может вызвать тупик.Особенно в случае NotifyIcon, поскольку при блокировке уведомляющего сообщения окно лотка зависает, и все значки перестают работать.

Далее вы видите модальный цикл COM, печально известный тем, что вызывает проблемы с повторным входом.Обратите внимание, как он вызывает PeekMessage (), вот как снова активируется обработчик событий MouseClick.

Что удивительно в этом стеке вызовов, так это отсутствие свидетельств перехода оператора lock в код, которыйвызывает CoWaitForMultipleHandles.Это как-то сделано самой Windows, я уверен, что в CLR для этого нет никаких условий.По крайней мере, не в версии SSCLI20.Это говорит о том, что Windows действительно обладает некоторыми встроенными знаниями о том, как CLR реализует класс Monitor.Довольно классная штука, без понятия, как они могли заставить это работать.Я подозреваю, что это исправляет адреса точек входа DLL для обнаружения кода.

Anyhoo, эти специальные контрмеры действуют только во время выполнения уведомления NotifyIcon.Обходной путь - отложить действие обработчика события до завершения обратного вызова.Вот так:

    private void notifyIcon1_MouseClick(object sender, MouseEventArgs e) {
        this.BeginInvoke(new MethodInvoker(delayedClick));
    }
    private void delayedClick() {
        if (reentrancyDetected) System.Diagnostics.Debugger.Break();
        reentrancyDetected = true;
        lock (thisLock) {
            //do nothing
        }
        reentrancyDetected = false;
    }

Проблема решена.

4 голосов
/ 24 февраля 2011

У меня была та же проблема, и вы можете переопределить поведение всех ожидающих вызовов .NET, реализовав SynchronizationContext и установив его на текущий.

http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.aspx

Если вы установите для свойства IsWaitNotificationRequired значение true, то метод Wait в вашем SynchronizationContext будет вызываться платформой в любое время, когда ему потребуется выполнить вызов ожидания.

Документация немного отсутствует, но в основном поведение ожидания по умолчанию заключается в вызове CoWaitForMultipleHandles и возвращении результата. Вы можете выполнить собственную передачу сообщений и объекты MsgWaitForMultipleObject с соответствующими флагами, чтобы избежать отправки WM_PAINT во время ожидания.

...