2010-09-04 3 views
5

J'ai une application qui démarre System.Threading.Timer, puis cette minuterie toutes les 5 secondes lit des informations à partir d'une base de données liée et met à jour l'interface utilisateur graphique sur la forme d'application principale;Objet Exclusion et application multi-threads

Depuis le System.Threading.Timer créer un autre thread pour l'événement Tick, je dois utiliser Object.Invoke pour mettre à jour l'interface utilisateur sur la principale forme d'application avec le code comme ceci:

this.Invoke((MethodInvoker)delegate() 
    { 
     label1.Text = "Example"; 
    }); 

L'application fonctionne très bien, mais parfois, lorsque l'utilisateur ferme le formulaire principal, puis ferme l'application, si le deuxième thread sur l'événement timer_tick met à jour l'interface utilisateur sur le thread principal, l'utilisateur obtient une exception ObjectDisposedException.

Comment puis-je faire pour arrêter et fermer le temporisateur de filetage avant de fermer le formulaire principal et d'éviter que l'objet ne dispose d'exception?

+0

'System.Threading.Timer' n'a pas d'événement' Tick'. Le seul temporisateur BCL qui possède réellement l'événement 'Tick' est le' System.Windows.Forms.Timer'. Pouvez-vous préciser lequel vous utilisez? C'est important. –

Répondre

3

System.Timers.Timer est une classe horrible. Il n'y a pas de bon moyen de l'arrêter de manière fiable, il y a toujours une course et vous ne pouvez pas l'éviter. Le problème est que son événement Elapsed est levé à partir d'un thread de pool de threads. Vous ne pouvez pas prédire quand ce thread commence réellement à s'exécuter. Lorsque vous appelez la méthode Stop(), ce thread a peut-être déjà été ajouté au pool de threads mais n'a pas encore été exécuté. Il est soumis à la fois au planificateur de threads Windows et au planificateur de threads.

Vous ne pouvez même pas le résoudre de manière fiable en retardant arbitrairement la fermeture de la fenêtre.Le planificateur de pool de threads peut retarder l'exécution d'un thread de 125 secondes maximum dans les cas les plus extrêmes. Vous réduirez la probabilité d'une exception en retardant la fermeture de quelques secondes, ce ne sera pas zéro. Retarder la fermeture pendant 2 minutes n'est pas réaliste.

Ne l'utilisez pas. Soit utiliser System.Threading.Timer et en faire une minuterie à usage unique que vous redémarrez dans le gestionnaire d'événements. Ou utilisez un System.Windows.Forms.Timer, il est synchrone.

Un temporisateur WF devrait être votre choix ici parce que vous utilisez Control.Invoke(). La cible du délégué ne démarrera pas tant que le thread de votre interface utilisateur ne sera pas inactif. Le même comportement que vous obtiendrez d'un minuteur WF.

+0

Qu'est-ce que WF Timer? est égal à DispatcherTimer? J'utilise en fait System.Threading.Timer et pas System.Timers.Timer. – aleroot

+0

Windows Forms Timer. Oui, même chose que le WPF DispatcherTimer. J'ai expliqué comment utiliser un System.Threading.Timer, définissez la période à 0. –

+0

Maintenant, je démarre le System.Threading.Timer ainsi: System.Threading.TimerCallback oCallbackGrid = new System.Threading.TimerCallback (GridTimer_Tick); Timer timerRefreshGrid = new System.Threading.Timer (oCallbackGrid, null, 1000, 3000); parce que je dois tirer GridTimer_Tick toutes les 3 secondes. Comment puis-je faire autrement? – aleroot

7

C'est un peu d'une proposition délicate que vous devez vous assurer ce qui suit sur un événement donné Fermer

  1. Le chronomètre est arrêté. Ceci est assez simple
  2. Le contrôle en cours de mise à jour n'est pas éliminé lorsque le délégué est exécuté. Encore une fois simple.
  3. Le code en cours d'exécution d'une coche de minuterie est terminé. Ceci est plus difficile mais faisable
  4. Il n'y a pas de méthode Invoke en attente. Ceci est un peu plus difficile à accomplir

J'ai déjà rencontré ce problème et j'ai trouvé que la prévention de ce problème est très problématique et implique beaucoup de désordre, difficile à maintenir le code. Il est beaucoup plus facile d'attraper les exceptions qui peuvent découler de cette situation. En général, je le fais en enveloppant la méthode Invoke comme suit

static void Invoke(ISynchronizedInvoke invoke, MethodInvoker del) { 
    try { 
    invoke.Invoke(del,null); 
    } catch (ObjectDisposedException) { 
    // Ignore. Control is disposed cannot update the UI. 
    } 
} 

Il n'y a rien de mal en soi d'ignorer cette exception si vous êtes à l'aise avec les conséquences. C'est si votre confortable avec l'interface utilisateur ne met pas à jour après qu'il a déjà été disposé. Je suis certainement :)

Ce qui précède ne prend pas en charge le problème n ° 2 et doit toujours être fait manuellement dans votre délégué. Lorsque je travaille avec WinForms, j'utilise souvent la surcharge suivante pour supprimer également cette vérification manuelle.

static void InvokeControlUpdate(Control control, MethodInvoker del) { 
    MethodInvoker wrapper =() => { 
    if (!control.IsDisposed) { 
     del(); 
    } 
    }; 
    try { 
    control.Invoke(wrapper,null); 
    } catch (ObjectDisposedException) { 
    // Ignore. Control is disposed cannot update the UI. 
    } 
} 

Remarque

Comme Hans noté ObjectDisposedException n'est pas la seule exception qui peut être soulevée de la méthode Invoke. Il y en a plusieurs autres, y compris au moins que vous devez considérer.

+0

+1: Ai-je raison de penser que pour le second exemple, si nous supposons que le seul thread qui va disposer d'un objet UI est le thread UI (qui * devrait * être le cas), vous ne devriez jamais voir un ' ObjectDisposedException', car la vérification de 'control.IsDisposed' est effectuée sur le thread d'interface utilisateur? –

+0

Ce n'est pas la seule exception qui peut être levée, vous obtiendrez aussi InvalidOperationException. –

+0

@Hans, oublié à ce sujet, a ajouté une note. – JaredPar

1

Créez deux booléens appelés 'StopTimer' et 'TimerStopped'. Définissez la propriété AutoReset du temporisateur sur false. Ensuite, formatez la méthode à ce qui suit Elapsed:

TimerStopped = false; 
Invoke((MethodInvoker)delegate { 
    // Work to do here. 
}); 
if (!StopTimer) 
    timer.Start(); 
else 
    TimerStopped = true; 

De cette façon, vous empêchez une condition de course, vérifier si la minuterie doit se poursuivre et rapports lorsque la méthode a atteint sa fin.

formatte votre événement FormClosing comme suit:

if (!TimerStopped) 
{ 
    StopTimer = true; 
    Thread waiter = new Thread(new ThreadStart(delegate { 
     while (!TimerStopped) { } 
     Invoke((MethodInvoker)delegate { Close(); }); 
    })); 
    waiter.Start(); 
    e.Cancel = true; 
} 
else 
    timer.Dispose(); 

Si la minuterie n'a pas encore arrêté, un thread est lancé d'attendre jusqu'à ce qu'il l'a fait et puis essayer de fermer à nouveau le formulaire.

Questions connexes