.FILET:Comment appeler un délégué sur un fil de discussion spécifique ?(ISynchronizeInvoke, Dispatcher, AsyncOperation, SynchronizationContext, etc.)

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

Question

Notez tout d'abord que cette question n'est pas taguée ou ou toute autre chose spécifique à l'interface graphique.C'est intentionnel, comme vous le verrez bientôt.

Deuxièmement, désolé si cette question est un peu longue.J'essaie de rassembler diverses informations qui circulent ici et là afin de fournir également des informations précieuses.Cependant, ma question se trouve juste sous « Ce que j'aimerais savoir ».

J'ai pour mission de comprendre enfin les différentes manières proposées par .NET d'appeler un délégué sur un thread spécifique.


Ce que j'aimerais savoir :

  • Je recherche le moyen le plus général possible (qui n'est pas spécifique à Winforms ou WPF) pour appeler des délégués sur des threads spécifiques.

  • Ou, formulé différemment :Je serais intéressé de savoir si, et comment, les différentes manières de procéder (par exemple via WPF Dispatcher) se servent les uns des autres ;c'est-à-dire s'il existe un mécanisme commun pour l'invocation de délégués inter-threads qui est utilisé par tous les autres.


Ce que je sais déjà :

  • Il existe de nombreux cours liés à ce sujet ;parmi eux:

    • SynchronizationContext (dans System.Threading)
      Si je devais deviner, ce serait la réponse la plus élémentaire ;même si je ne comprends pas exactement ce qu'il fait, ni comment il est utilisé.

    • AsyncOperation & AsyncOperationManager (dans System.ComponentModel)
      Ceux-ci semblent être des emballages SynchronizationContext.Aucune idée de comment les utiliser.

    • WindowsFormsSynchronizationContext (dans System.Windows.Forms)
      Une sous-classe de SynchronizationContext.

    • ISynchronizeInvoke (dans System.ComponentModel)
      Utilisé par Windows Forms.(Le Control la classe implémente cela.Si je devais deviner, je dirais que cette implémentation utilise WindowsFormsSynchronizationContext.)

    • Dispatcher &DispatcherSynchronizationContext (dans System.Windows.Threading)
      On dirait que ce dernier est une autre sous-classe de SynchronizationContext, et les anciens délégués.

  • Certains threads ont leur propre boucle de messages, ainsi qu'une file d'attente de messages.

    (La page MSDN À propos des messages et des files d'attente de messages contient quelques informations générales d'introduction sur le fonctionnement des boucles de messages au niveau du système, c'est-à-direfiles d'attente de messages comme l'API Windows.)

    Je peux voir comment implémenter l'invocation cross-thread pour les threads avec une file d'attente de messages.À l'aide de l'API Windows, vous placeriez un message dans la file d'attente des messages d'un fil de discussion spécifique via PostThreadMessage qui contient une instruction pour appeler un délégué.La boucle de messages — qui s'exécute sur ce fil — finira par accéder à ce message et le délégué sera appelé.

    D'après ce que j'ai lu sur MSDN, un fil de discussion ne dispose pas automatiquement de sa propre file d'attente de messages.Une file d'attente de messages deviendra disponible, par ex.lorsqu'un thread a créé une fenêtre.Sans file d'attente de messages, cela n'a aucun sens pour un fil d'avoir une boucle de messages.

    Alors, l’invocation de délégués inter-threads est-elle possible lorsque le thread cible n’a pas de boucle de message ?Disons, dans une application console .NET ?(À en juger par les réponses à cette question, je suppose que c'est effectivement impossible avec les applications console.)

Était-ce utile?

La solution

Désolé d'avoir posté une réponse aussi longue.Mais j'ai pensé qu'il valait la peine d'expliquer ce qui se passe exactement.

A-ha!Je pense que j'ai compris.La manière la plus générique d'invoquer un délégué sur un thread spécifique semble en effet être la SynchronizationContext classe.

Premièrement, le framework .NET fait pas fournir un moyen par défaut pour simplement "envoyer" un délégué à n'importe lequel thread de telle sorte qu'il y soit exécuté immédiatement.Évidemment, cela ne peut pas fonctionner, car cela signifierait « interrompre » le travail que ce thread effectuerait à ce moment-là.Par conséquent, le thread cible décide lui-même comment et quand il « recevra » les délégués ;c'est-à-dire que cette fonctionnalité doit être fournie par le programmeur.

Ainsi, un thread cible a besoin d’un moyen de « recevoir » les délégués.Cela peut être fait de différentes manières.Un mécanisme simple consiste pour le thread à revenir toujours à une boucle (appelons-la la "boucle de messages") où il consultera une file d'attente.Cela fonctionnera avec tout ce qui est dans la file d'attente.Windows fonctionne nativement comme ceci en ce qui concerne les éléments liés à l'interface utilisateur.

Dans ce qui suit, je vais montrer comment implémenter une file d'attente de messages et un SynchronizationContext pour cela, ainsi qu'un fil de discussion avec une boucle de messages.Enfin, je montrerai comment appeler un délégué sur ce fil.


Exemple:

Étape 1. Commençons par créer un SynchronizationContext classe qui sera utilisé avec la file d'attente de messages du thread cible :

class QueueSyncContext : SynchronizationContext
{
    private readonly ConcurrentQueue<SendOrPostCallback> queue;

    public QueueSyncContext(ConcurrentQueue<SendOrPostCallback> queue)
    {
        this.queue = queue;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        queue.Enqueue(d);
    }

    // implementation for Send() omitted in this example for simplicity's sake.
}

Fondamentalement, cela ne fait pas plus que ajouter tous les délégués transmis via Post à une file d'attente fournie par l'utilisateur.(Post est la méthode pour les appels asynchrones. Send serait pour les invocations synchrones.J'omets ce dernier pour l'instant.)

Étape 2. Écrivons maintenant le code pour un fil de discussion Z qui attend les délégués d arriver:

SynchronizationContext syncContextForThreadZ = null;

void MainMethodOfThreadZ()
{
    // this will be used as the thread's message queue:
    var queue = new ConcurrentQueue<PostOrCallDelegate>();

    // set up a synchronization context for our message processing:
    syncContextForThreadZ = new QueueSyncContext(queue);
    SynchronizationContext.SetSynchronizationContext(syncContextForThreadZ);

    // here's the message loop (not efficient, this is for demo purposes only:)
    while (true)
    {
        PostOrCallDelegate d = null;
        if (queue.TryDequeue(out d))
        {
            d.Invoke(null);
        }
    }
}

Étape 3. Fil Z il faut commencer quelque part :

new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();

Étape 4. Enfin, retour sur un autre fil UN, nous voulons envoyer un délégué au fil de discussion Z:

void SomeMethodOnThreadA()
{
    // thread Z must be up and running before we can send delegates to it:
    while (syncContextForThreadZ == null) ;

    syncContextForThreadZ.Post(_ =>
        {
            Console.WriteLine("This will run on thread Z!");
        },
        null);
}

Ce qui est bien, c'est que SynchronizationContext fonctionne, que vous soyez dans une application Windows Forms, dans une application WPF ou dans une application console multithread de votre propre conception.Winforms et WPF fournissent et installent des SynchronizationContexts pour leur fil principal/interface utilisateur.

La procédure générale pour appeler un délégué sur un thread spécifique est la suivante :

  • Vous devez capturer le thread cible (Z's) SynchronizationContext, afin que vous puissiez Send (de manière synchrone) ou Post (de manière asynchrone) un délégué à ce fil.La façon de procéder consiste à stocker le contexte de synchronisation renvoyé par SynchronizationContext.Current pendant que vous êtes sur le fil cible Z.(Ce contexte de synchronisation doit avoir été préalablement enregistré sur/par thread Z.) Ensuite, stockez cette référence quelque part où elle est accessible par thread UN.

  • Pendant que je suis sur le fil UN, vous pouvez utiliser le contexte de synchronisation capturé pour envoyer ou publier n'importe quel délégué dans le fil de discussion Z: zSyncContext.Post(_ => { ... }, null);

Autres conseils

Si vous souhaitez prendre en charge l'appel d'un délégué sur un fil de discussion qui n'a pas de boucle de message, vous devez essentiellement implémenter la vôtre.

Il n'y a rien de particulièrement magique dans une boucle de messages :c'est comme un consommateur dans un modèle producteur/consommateur normal.Il conserve une file d'attente de choses à faire (généralement des événements auxquels réagir) et il parcourt la file d'attente en agissant en conséquence.Lorsqu'il n'y a plus rien à faire, il attend que quelque chose soit placé dans la file d'attente.

Pour le dire autrement :vous pouvez considérer un thread avec une boucle de messages comme un pool de threads à un seul thread.

Vous pouvez l’implémenter vous-même assez facilement, y compris dans une application console.N'oubliez pas que si le thread parcourt la file d'attente de travail, il ne peut pas faire autre chose également - alors que généralement le thread principal d'exécution dans une application console est destiné à effectuer une séquence de tâches, puis à terminer.

Si vous utilisez .NET 4, il est très simple d'implémenter une file d'attente producteur/consommateur à l'aide du BlockingCollection classe.

Je suis récemment tombé sur cet article et j'ai trouvé qu'il m'avait sauvé la vie.L'utilisation d'une file d'attente simultanée bloquante est la sauce secrète, comme l'a souligné Jon Skeet ci-dessus.Le meilleur « comment faire » que j'ai trouvé pour faire fonctionner tout cela est Cet article sur CodeProject par Mike Peretz.L'article fait partie d'une série en trois parties sur SynchronizationContext qui fournit des exemples de code qui peuvent être facilement transformés en code de production.Notez seulement que Peretz remplit tous les détails, mais il nous rappelle également que la base SynchronizationContext a des implémentations essentiellement sans valeur de Post() et Send() et devrait donc vraiment être considérée comme une classe de base abstraite.Un utilisateur occasionnel de la classe de base pourrait être surpris de constater qu’elle ne résout pas les problèmes du monde réel.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top