web-dev-qa-db-fra.com

Dispatcher Invoke (...) vs BeginInvoke (...) confusion

Je ne comprends pas pourquoi je ne peux pas faire fonctionner cette application de compteur de tests avec 2 (ou plus) contre-boîtes en cours d'exécution simultanées avec l'utilisation de "BeginInvoke" sur mon répartiteur dans la méthode Count ().

Vous pouvez résoudre le problème en remplaçant BeginInvoke par un Invoke. Mais cela ne résout pas ma confusion.

Voici l'exemple de code dont je parle:

public class CounterTextBox : TextBox
{
    private int _number;

    public void Start()
    {
        (new Action(Count)).BeginInvoke(null, null);
    }

    private void Count()
    {
        while (true)
        {
            if (_number++ > 10000) _number = 0;
            this.Dispatcher.BeginInvoke(new Action(UpdateText), System.Windows.Threading.DispatcherPriority.Background, null);    
        }
    }

    private void UpdateText()
    {
        this.Text = "" + _number;
    }
}
28
Jerev

Lorsque vous utilisez Dispatcher.BeginInvoke cela signifie qu'il planifie l'action donnée pour exécution dans le thread d'interface utilisateur à un moment ultérieur, puis retourne le contrôle pour autoriser le thread actuel pour continuer à exécuter. Invoke bloque l'appelant jusqu'à la fin de l'action planifiée.

Lorsque vous utilisez BeginInvoke, votre boucle va s'exécuter super rapidement car BeginInvoke revient immédiatement. Cela signifie que vous ajoutez beaucoup et beaucoup d'actions au file d'attente de messages. Vous les ajoutez beaucoup plus rapidement qu'ils ne peuvent réellement être traités. Cela signifie qu'il y a beaucoup de temps entre le moment où vous planifiez un message et celui où il a réellement une chance d'être exécuté.

L'action réelle que vous exécutez utilise le champ _number. Mais _number est modifié par l'autre thread très rapidement et alors que l'action est dans la file d'attente . Cela signifie qu'il n'affichera pas la valeur de _number au moment où vous avez planifié l'action, mais plutôt ce qu'elle est après qu'elle se soit poursuivie dans sa boucle très serrée.

Si tu utilises Dispatcher.Invoke au lieu de cela, il empêche la boucle de "prendre de l'avance sur elle-même" et d'avoir plusieurs événements planifiés, ce qui garantit que la valeur qu'elle écrit est toujours la valeur "actuelle". De plus, en forçant chaque itération de la boucle à attendre que le message soit exécuté, cela rend la boucle beaucoup moins "serrée", donc elle ne peut pas s'exécuter aussi rapidement en général.

Si vous voulez utiliser BeginInvoke la première chose que vous devez vraiment faire est de ralentir votre boucle. Si vous voulez qu'il mette à jour le texte toutes les secondes, ou tous les 10 ms, ou autre chose, vous pouvez utiliser Thread.Sleep pour attendre la durée appropriée.

Ensuite, vous devez prendre une copie de _number avant de le passer à Dispatcher pour qu'il affiche la valeur au moment où vous l'avez planifié, pas au moment de son exécution:

while (true)
{
    if (_number++ > 10000)
        _number = 0;
    int copy = _number;
    this.Dispatcher.BeginInvoke(new Action(() => UpdateText(copy))
        , System.Windows.Threading.DispatcherPriority.Background, null);
    Thread.Sleep(200);
}

private void UpdateText(int number)
{
    this.Text = number.ToString();
}
71
Servy