Comment éviter les fuites de descripteurs lors de l'appel dans l'interface utilisateur à partir de System.Threading.Timer?

StackOverflow https://stackoverflow.com/questions/1603123

Question

Il semble que l'appel d'Invoke sur un contrôle winforms dans un rappel depuis un System.Threading.Timer laisse échapper des poignées jusqu'à ce que le timer soit supprimé. Quelqu'un at-il une idée de la façon de contourner ce problème? Je dois interroger une valeur par seconde et mettre à jour l'interface utilisateur en conséquence.

Je l'ai essayé dans un projet test pour m'assurer que c'était bien la cause de la fuite, qui est simplement la suivante:

    System.Threading.Timer timer;
    public Form1()
    {
        InitializeComponent();
        timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500);
    }
    void DoStuff(object o)
    {
        this.Invoke(new Action(() => this.Text = "hello world"));
    }

Il y aura une fuite de 2 handles / seconde si vous regardez dans le gestionnaire de tâches Windows.

Était-ce utile?

La solution

Invoke agit comme une paire BeginInvoke / EndInvoke dans la mesure où il poste le message sur le thread d'interface utilisateur, crée un handle et attend ce handle pour déterminer le moment où la méthode Invoked est terminée. C’est cette poignée qui "fuit". Vous pouvez constater qu'il s'agit d'événements non nommés utilisant Explorateur de processus pour surveiller les descripteurs pendant que l'application est en cours d'exécution.

Si IASyncResult était IDisposable, la mise au rebut de l’objet se chargerait du nettoyage de la poignée. Comme ce n'est pas le cas, les descripteurs sont nettoyés lorsque le ramasse-miettes s'exécute et appelle le finaliseur de l'objet IASyncResult. Vous pouvez le voir en ajoutant un GC.Collect () après 20 appels à DoStuff - le nombre de descripteurs diminue toutes les 20 secondes. Bien sûr, " résolution " le problème en ajoutant des appels à GC.Collect () est le moyen mauvais de résoudre le problème; laissez le ramasse-miettes faire son propre travail.

Si vous n'avez pas besoin de l'appel Invoke pour être synchrone, utilisez BeginInvoke au lieu de Invoke et n'appelez pas EndInvoke; le résultat final fera la même chose, mais aucun descripteur ne sera créé ou "fuit".

Autres conseils

Y a-t-il une raison pour laquelle vous ne pouvez pas utiliser un System.Windows.Forms.Timer ici? Si le minuteur est lié à ce formulaire, vous n'aurez même pas besoin d'appeler.

D'accord, je lui ai donné un peu plus de temps et il semble que ce ne soit pas une fuite de poignées, c'est simplement la nature indéterminée du ramasse-miettes. Je l’ai frappé jusqu’à 10 ms par coup et il montait très vite et 30 secondes plus tard, il retombait.

POUR confirmer la théorie, j’ai appelé manuellement GC.Collect () à chaque rappel (ne faites pas cela dans de vrais projets, c’était juste pour tester, il existe de nombreux articles sur la raison pour laquelle c’est une mauvaise idée) et le nombre de descripteurs était stable.

Intéressant - Ce n'est pas une réponse, mais d'après le commentaire d'Andrei, j'aurais pensé que les fuites ne seraient pas traitées de la même manière, mais qu'elles le feraient au même rythme que l'OP mentionné.

System.Threading.Timer timer;
    public Form2()
    {
        InitializeComponent();

    }

    private void UpdateFormTextCallback()
    {
        this.Text = "Hello World!";
    }

    private Action UpdateFormText;

    private void DoStuff(object value)
    {
        this.Invoke(UpdateFormText);
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500);
        UpdateFormText = new Action(UpdateFormTextCallback);
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top