3 votes

L'objet temporisateur existe au-delà de la durée de vie de l'objet fenêtre

Je montre un Forms fenêtre comme boîte de dialogue

private void buttonOverview_Click(object sender, EventArgs e)
{
    (new OverviewBox()).ShowDialog();
    MessageBox.Show("Window Exited");
}

OverviewBox possède une minuterie de rafraîchissement qui est instanciée dans le constructeur.

public OverviewBox()
{
    InitializeComponent();

    this._polltimer = new Timer { Interval = 30000, Enabled = true };
    this._polltimer.Tick += (sender, e) => { this.Poll(); };
}

La méthode Poll obtient de manière asynchrone des données de la base de données et met à jour la vue sans la geler.

private void Poll()
{
    Task.Run(() =>
    {
        if (!SessionContext.Connectable())
        {
            return;
        }
        try
        {
            [logics to get data]
            this.dgvChangeCoordinators.BeginInvoke(new Action(() => { SetDataGridView(this.dataGridView, "<Data Description>", listwithdata); }));
        }
        catch (Exception ex)
        {
            Logger.Log(ex.ToString());
            throw;
        }
    });
}

SetDataGridView définit la liste comme itemsource d'un datagridview et affiche la description des données. Parfois cependant, mes utilisateurs se plaignent d'exceptions. Le journal des exceptions ressemble à ceci :

7/15/2013 5:00:10 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()

7/15/2013 5:00:23 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()

7/15/2013 5:00:40 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()

7/15/2013 5:00:53 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()

D'après la différence de temps entre les exceptions, je conclus qu'au moins deux instances de la minuterie sont toujours actives (30 secondes entre les sondages, 4 temps différents pour lesquels les groupes de 2 sont dans les 30 secondes, l'intervalle de sondage). Cependant, je ne peux pas simuler le problème en démarrant et en fermant simplement la synthèse deux fois.

Je soupçonne un GC Un problème connexe se pose lorsque l'objet fenêtre est collecté à un moment donné, mais que le poller continue d'exister. Lorsqu'il tente de mettre à jour la fenêtre dans le contexte du fil de la fenêtre, il échoue. Mais alors, l'objet Window et tout son contenu ne devraient-ils pas exister uniquement dans le contexte de private void ? buttonOverview_Click ? Nous avons ajouté un appel MessageBox.Show() à la méthode du bouton pour tester si la méthode est terminée après la fermeture de la boîte de dialogue. Il s'affiche effectivement.

Définir un point d'arrêt sur le Poll pour voir si elle était toujours appelée après la fermeture de la boîte de dialogue. C'était le cas, donc le poller vit définitivement plus longtemps que la fenêtre est visible. Ma question est la suivante : mes conclusions sont-elles correctes jusqu'à présent ? Si oui, comment le poller peut-il continuer à exister même si le contexte dans lequel l'objet créant le timer a été instancié n'existe plus, par exemple, comment empêcher le poller de vivre bien après la fermeture de la fenêtre ? Je pense à un événement de déchargement mais je ne sais pas si c'est la meilleure solution.

5voto

Julien Lebosquain Points 20894

Tout d'abord, le Garbage Collector n'est pas déterministe. Même si votre fenêtre est fermée et n'est référencée nulle part, il peut s'écouler un long moment avant que la fenêtre ne soit effectivement collectée. Vous devriez vous désabonner de la Tick et définir IsEnabled a false dès que la fenêtre est fermée.

Ceci étant dit, le vrai problème ici est le System.Windows.Forms.Timer même. Dès qu'elle est activée, elle alloue un fichier GCHandle pour lui-même, empêchant ainsi sa collecte de déchets. Le gestionnaire d'événement empêche alors la collecte de la fenêtre, et non l'inverse comme c'est généralement le cas, et comme vous pensez que cela se produit.

Notez que System.Windows.Forms.Timer se désactive lorsqu'il est éliminé, ce qui permet d'éviter ce problème, et tous les composants d'un système de gestion de l'eau sont désactivés. Form sont automatiquement éliminés lorsque le formulaire se ferme. Mais vous n'enregistrez pas le Timer en tant que composant de formulaire, donc Dispose n'est jamais appelé automatiquement. Vous devez ajouter un Timer à votre formulaire par le biais de la boîte à outils, ou l'instancier en utilisant new Timer(components) pour voir le problème disparaître.

0voto

RB84 Points 301

Il faut transformer le tick en une méthode nommée

this._polltimer.Tick += _polltimer_Tick;

Et désenregistrer lors de la fermeture du formulaire

    private void OverviewBox_FormClosed(object sender, FormClosedEventArgs e)
    {
        this._polltimer.Tick -= this._polltimer_Tick;
    }

Le fait de ne pas se désenregistrer empêche la destruction du Timer, car il existe toujours des références actives à celui-ci. La fermeture du formulaire détruit cependant les éléments du formulaire, ce qui cause le problème lors de l'appel de invoke.

Un grand merci à toutes les personnes qui ont répondu, en particulier à @BenjaminBaumann qui a fourni l'indice qui a conduit à cette réponse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X