2010-06-15 2 views
6

Dans quelles circonstances la mise à jour d'un contrôle d'interface utilisateur à partir d'un thread non UI peut-elle entraîner une augmentation continue des handles des processus, lors de l'utilisation d'un délégué et .InvokeRequired?Thread UI .Invoke() provoquant une fuite du handle?

Par exemple:

public delegate void DelegateUIUpdate(); 
private void UIUpdate() 
{ 
    if (someControl.InvokeRequired) 
    { 
     someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 
     return; 
    } 
    // do something with someControl 
} 

Lorsque cela est appelé dans une boucle ou sur des intervalles de minuterie, les poignées du programme augmentent constamment.

EDIT:

Si ce qui précède est commenté et modifié en tant que tel:

public delegate void DelegateUIUpdate(); 
private void UIUpdate() 
{ 
    //if (someControl.InvokeRequired) 
    //{ 
    // someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 
    // return; 
    //} 
    CheckForIllegalCrossThreadCalls = false; 
    // do something with someControl 
} 

... alors les poignées arrêter incrémenter, mais je ne veux pas laisser croix thread appels, bien sûr.

EDIT 2:

Voici un exemple qui montre les poignées augmentent:

Thread thread; 
private delegate void UpdateGUI(); 
bool UpdateTheGui = false; 

public Form1() 
{ 
    InitializeComponent(); 

    thread = new Thread(new ThreadStart(MyThreadLoop)); 
    thread.Start(); 
} 

private void MyThreadLoop() 
{ 
    while (true) 
    { 
     Thread.Sleep(500); 
     if (UpdateTheGui) 
     { 
      UpdateTheGui = false; 
      UpdateTheGuiNow(); 
     } 
    } 
} 

private void UpdateTheGuiNow() 
{ 
    if (label1.InvokeRequired) 
    { 
     label1.Invoke(new UpdateGUI(UpdateTheGuiNow)); 
     return; 
    } 

    label1.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
    label2.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
    label3.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
} 

private void btnInvoke_Click(object sender, EventArgs e) 
{ 
    UpdateTheGui = true; 
} 
+0

J'ai le même problème ici, avec exactement le même appel sur une minuterie. Merci d'avoir mentionné CheckForIllegalCrossThreadCalls parce que je n'en avais jamais entendu parler auparavant. – muusbolla

Répondre

3

La méthode Control.Invoke() ne consomme aucun descripteur. Cependant, ce code est clairement appelé à partir d'un fil. Un fil est-ce que consomme des poignées, 5 d'entre eux.

La classe Thread n'a pas de méthode Dispose(), bien qu'elle doive en avoir une. C'était probablement par conception, il serait très difficile d'appeler de manière fiable, de manière impossible pour threads pool de threads. Les 5 poignées dont un thread a besoin sont libérées par le finaliseur. Votre programme nécessitera des quantités croissantes de poignées si le finaliseur ne fonctionne jamais.

Ne pas terminer l'exécution du finaliseur est assez inhabituel. Vous devriez avoir un programme qui démarre beaucoup de threads mais n'alloue pas beaucoup de mémoire. Cela a tendance à se produire uniquement dans les tests statiques. Vous pouvez diagnostiquer cette condition avec Perfmon.exe, utiliser les compteurs de performance de mémoire .NET et vérifier si les collections gen # 0 sont en cours.

Si cela se produit dans un programme de production, vous devrez appeler GC.Collect() vous-même pour éviter une fuite du handle.

+0

J'ai utilisé le modèle décrit pour mettre à jour les composants de l'interface utilisateur dans le passé, sans le problème d'augmentation du handle, mais dans le projet actuel, il est très certainement lié à cet appel de thread à interface utilisateur. Si je commente la partie qui vérifie et appelle le délégué, et à la place 'CheckForIllegalCrossThreadCalls = false;' alors les poignées cessent d'augmenter, bien que je ne veux pas le laisser dans un tel état. Je vais mettre à jour la question avec cette info. – JYelton

+0

Eh bien, cela défie une explication facile. Donnez-nous une idée de ce que fait le reste du code et à quelle instruction spécifique vous voyez l'augmentation du nombre de handles. –

+0

Ajout d'un exemple montrant le problème plus en détail. Un bouton sur le formulaire déclenche la mise à jour. – JYelton

0

C'est le modèle standard pour l'utilisation Invoke mises à jour Marshall au thread d'interface utilisateur. Etes-vous sûr que votre problème n'est pas causé par d'autres codes de votre application qui ne sont pas inclus dans votre question?

+0

Pas tout à fait sûr, mais voir le modifier à la question et aussi le commentaire à la réponse de Hans. C'est un problème très inhabituel. – JYelton

0

Je ne pense pas que ce soit lié. Il suffit peut-être d'attendre que le garbage collector dispose des nouveaux objets alloués dans Invoke().

+0

L'appel 'GC.Collect()' va en réalité empêcher les poignées d'augmenter. Il est intéressant de noter que si GC.Collect() est ajouté après que le programme a fonctionné pendant un certain temps, il ne réduit pas les poignées à un état initial, mais les empêche simplement de s'accumuler. – JYelton

0

Je vois réellement le même problème se produisant que JYelton. J'ai le même appel à partir d'un thread pour mettre à jour l'interface utilisateur.

Dès que la ligne someControl.Invoke(new DelegateUIUpdate(UIUpdate)); est appelée, la poignée augmente de un. Il y a certainement une fuite quelconque sur l'invocation, mais je n'ai aucune idée de ce qui la cause. Cela a été vérifié sur plusieurs systèmes.

2

J'ai vu la même chose dans mon code.Je l'ai corrigé en remplaçant Invoke par BeginInvoke. La fuite de poignée est partie.

Doron.

3

J'ai eu le même problème avec

this.Invoke(new DelegateClockUpdate(ChangeClock), sender, e); 

créer une poignée chaque appel.

Le handle s'incrémente car Invoke est synchrone et le handle a été laissé en suspens.

Soit un handle d'attente doit être utilisé pour traiter le résultat ou la méthode BeginInvoke asynchrone comme indiqué ci-dessous.

this.BeginInvoke(new DelegateClockUpdate(ChangeClock), sender, e);  
+0

Merci, mec!Votre réponse est tellement cool, qui a sauvé mon application de OutOfMemory et 3 jours d'enquêtes. Probablement, vous connaissez un certain ninjustsu profilage .... Vous êtes un vrai coquin! –

0

Appel Aync avec finalisation explicite. Exapmle:

public static class ActionExtensions 
    { 
    private static readonly ILog log = LogManager.GetLogger(typeof(ActionExtensions)); 

    /// <summary> 
    /// Async exec action. 
    /// </summary> 
    /// <param name="action">Action.</param> 
    public static void AsyncInvokeHandlers(
     this Action action) 
    { 
     if (action == null) 
     { 
     return; 
     } 

     foreach (Action handler in action.GetInvocationList()) 
     { 
     // Initiate the asychronous call. Include an AsyncCallback 
     // delegate representing the callback method, and the data 
     // needed to call EndInvoke. 
     handler.BeginInvoke(
      ar => 
      { 
      try 
      { 
       // Retrieve the delegate. 
       var handlerToFinalize = (Action)ar.AsyncState; 
       // Call EndInvoke to free resources. 
       handlerToFinalize.EndInvoke(ar); 

       var handle = ar.AsyncWaitHandle; 
       if (handle.SafeWaitHandle != null && !handle.SafeWaitHandle.IsInvalid && !handle.SafeWaitHandle.IsClosed) 
       { 
       ((IDisposable)handle).Dispose(); 
       } 
      } 
      catch (Exception exception) 
      { 
       log.Error("Async Action exec error.", exception); 
      } 
      }, 
      handler); 
     } 
    } 
    } 

Voir http://msdn.microsoft.com/en-us/library/system.iasyncresult.asyncwaithandle.aspx Note:

Lorsque vous utilisez la méthode BeginInvoke d'un délégué à appeler une méthode de manière asynchrone et obtenir une poignée d'attente de la nous recommandons que IAsyncResult résultant, vous fermez l'attente gérer dès que vous avez fini de l'utiliser, en appelant la méthode WaitHandle.Close. Si vous libérez simplement toutes les références au handle d'attente, les ressources système sont libérées lorsque garbage collection récupère le handle d'attente, mais la récupération de place fonctionne plus efficacement lorsque les objets jetables sont explicitement fermés ou éliminés. Pour plus d'informations, consultez la propriété AsyncResult.AsyncWaitHandle.

0

est ici une méthode d'extension qui fonctionne de façon similaire à l'appel Invoke normal, mais va nettoyer la poignée après:

namespace ExtensionMethods 
{ 
    public static class ExtensionMethods 
    { 
     public static void InvokeAndClose(this Control self, MethodInvoker func) 
     { 
      IAsyncResult result = self.BeginInvoke(func); 
      self.EndInvoke(result); 
      result.AsyncWaitHandle.Close(); 
     } 
    } 
} 

Vous pouvez alors l'appeler de façon très similaire à un Invoke normal:

myForm.InvokeAndClose((MethodInvoker)delegate 
{ 
    someControl.Text = "New Value"; 
}); 

Il bloque et attend l'exécution du délégué, puis ferme le handle avant de revenir.

Questions connexes