2010-09-06 14 views
3

J'ai trouvé un problème de réentrance lors de l'utilisation de NotifyIcons. Il est vraiment facile de reproduire, juste déposer un NotiftIcon sur un formulaire et l'événement click devrait ressembler à ceci:Réentrance Gui avec attente gérée

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

commencent également un fil d'arrière-plan qui provoquera une affirmation:

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

Maintenant, si vous commencer à cliquer sur l'notifyicon le message apparaîtra, indiquant la réentrance. Je suis conscient des raisons pour lesquelles l'attente gérée dans STA devrait pomper des messages pour certaines fenêtres. Mais je ne suis pas sûr pourquoi les messages de notifyicon sont pompés. Existe-t-il également un moyen d'éviter le pompage sans utiliser d'indicateurs booléens lors de l'entrée/de la sortie des méthodes?

Répondre

5

Vous pouvez voir ce qui se passe si vous remplacez l'appel MessageBox.Show par Debugger.Break et attachez un débogueur avec le débogage natif activé lorsque la coupure frappe. La pile d'appel ressemble à ceci:

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 
[email protected]() + 0x23 bytes 
[email protected]() + 0xb3 bytes 
[email protected]() + 0x4b bytes  
[email protected]() + 0x24 bytes 
[email protected]() + 0x2e bytes 
[email protected]() + 0xc bytes 
[email protected]() + 0x2d bytes 
[email protected]() + 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 
[email protected]() - 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# 

La fonction correspondante est CoWaitForMultipleHandles. Il s'assure que le thread STA ne peut pas bloquer sur un objet de synchronisation sans toujours pomper des messages. Qui est très malsain car il est si susceptible de provoquer l'impasse. Notamment dans le cas de NotifyIcon puisque le blocage du message de notification bloquerait la fenêtre de la barre d'état, rendant toutes les icônes inopérantes. Ce que vous voyez ensuite est la boucle modale COM, tristement célèbre pour causer des problèmes de ré-entrée. Notez comment il appelle PeekMessage(), c'est ainsi que le gestionnaire d'événements MouseClick est à nouveau activé. Ce qui est plutôt étonnant dans cette pile d'appels, c'est qu'il n'y a aucune preuve que l'instruction lock est en train de passer au code qui appelle CoWaitForMultipleHandles. C'est en quelque sorte fait par Windows lui-même, je suis à peu près sûr que le CLR n'a aucune disposition pour cela. Au moins pas dans la version SSCLI20. Il suggère que Windows a en fait une connaissance intégrée de la façon dont le CLR implémente la classe Monitor. Des trucs géniaux, aucune idée de comment ils pourraient faire ce travail. Je suspecte qu'il corrige des adresses entrypoint de DLL pour réviser le code.

De toute façon, ces contre-mesures spéciales ne sont actives que lorsque la notification NotifyIcon est exécutée. Une solution de contournement consiste à retarder l'action du gestionnaire d'événements jusqu'à ce que le rappel soit terminé. Comme ceci:

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

Problème résolu.

+1

Merci beaucoup.Tout à fait logique, nous transmettons donc le message aux formulaires WndProc où il n'y a pas de réentrée. En passant, il y a quelques bonnes ressources sur le blocage géré ici: http://blogs.msdn.com/b/cbrumme/archive/2003/04/17/51361.aspx et ici: http://blogs.msdn.com /b/cbrumme/archive/2004/02/02/66219.aspx – jomi

4

J'ai rencontré le même problème et vous pouvez réellement remplacer le comportement de tous les appels d'attente .NET en implémentant un SynchronizationContext et en le définissant sur celui en cours.

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

Si vous définissez la propriété IsWaitNotificationRequired true alors la méthode d'attente sur votre SynchronizationContext sera appelé par le cadre à tout moment, il doit effectuer un appel d'attente.

La documentation manque un peu, mais le comportement par défaut de wait est d'appeler CoWaitForMultipleHandles et de retourner le résultat. Vous pouvez exécuter votre propre message de pompage et MsgWaitForMultipleObjects avec les indicateurs appropriés ici pour éviter que WM_PAINT soit distribué pendant l'attente.

+1

Grande suggestion. Pour un peu plus de détails sur la façon de le faire lire http://joeduffyblog.com/2008/02/27/hooking-clr-blocking-calls-with-synchronizationcontext/ –