.NET:Come faccio a richiamare un delegato in un thread specifico?(ISynchronizeInvoke, Dispatcher, AsyncOperation, SynchronizationContext, etc.)

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

Domanda

Si noti prima di tutto che questa domanda non è tagged o o qualsiasi altra cosa GUI-specifici.Questo è intenzionale, come si vedrà a breve.

Secondo, scusate se questa domanda è un po ' lungo.Cerco di mettere insieme i vari bit di informazioni in giro di qua e di là, così come anche fornire informazioni preziose.La mia domanda, però, è a destra sotto "Quello che vorrei sapere".

Io sono su una missione per capire finalmente i vari modi offerti da .NET per richiamare un delegato in un thread specifico.


Quello che vorrei sapere:

  • Sto cercando il modo più generale possibile (che non è Winforms o WPF-specifici) per richiamare i delegati sul thread specifici.

  • O, formulata in modo diverso:Io sarei interessato se e come i vari modi per fare questo (come via di WPF Dispatcher far uso di ogni altro;che è, se c'è un meccanismo comune per il cross-thread delegato invocazione che viene utilizzato da tutti gli altri.


Quello che so già:

  • Ci sono tante classi relative a questo argomento;tra di loro:

    • SynchronizationContext (in System.Threading)
      Se dovessi indovinare, che sarebbe la più semplice;anche se non capisco che cosa esattamente si fa, né di come viene utilizzato.

    • AsyncOperation & AsyncOperationManager (in System.ComponentModel)
      Questi sembrano essere i wrapper intorno SynchronizationContext.Non ho idea di come usarli.

    • WindowsFormsSynchronizationContext (in System.Windows.Forms)
      Una sottoclasse di SynchronizationContext.

    • ISynchronizeInvoke (in System.ComponentModel)
      Utilizzato da Windows Form.(Il Control classe implementa questo.Se devo indovinare, direi che questa implementazione si avvale di WindowsFormsSynchronizationContext.)

    • Dispatcher &DispatcherSynchronizationContext (in System.Windows.Threading)
      Sembra che quest'ultimo è un'altra sottoclasse di SynchronizationContext, e l'ex delegati a farlo.

  • Alcuni thread hanno un proprio ciclo di messaggi, con una coda di messaggi.

    (La pagina di MSDN I Messaggi e le Code di Messaggi ha introduttivo con informazioni di base su come messaggio di cicli di lavoro a livello di sistema, vale a direcode di messaggi, come le API di Windows.)

    Posso vedere come verrebbe da implementare cross-thread invocazione per la filettatura con una coda di messaggi.Utilizzando le API di Windows, si dovrebbe mettere un messaggio in una specifica coda di messaggi del thread tramite PostThreadMessage che contiene un'istruzione di chiamata di qualche delegato.Il ciclo di messaggi — che corre sul filo — sarà finalmente arrivare a quel messaggio, e il delegato, sarà chiamato.

    Da quello che ho letto su MSDN, un thread non hanno automaticamente la propria coda di messaggi.Una coda di messaggi, che saranno disponibili per es.quando un thread si è creata una finestra.Senza una coda di messaggi, non ha senso per un thread per avere un ciclo di messaggi.

    Così, è cross-thread delegato invocazione del tutto possibile quando il thread di destinazione non ha alcun ciclo di messaggi?Diciamo che, in un .NET applicazione console?(A giudicare dalle risposte a questa domanda, Suppongo che è davvero impossibile con le applicazioni per console.)

È stato utile?

Soluzione

Ci dispiace per la pubblicazione ad una lunga risposta.Ma ho pensato che vale la pena di spiegare che cosa esattamente sta succedendo.

A-ha!Credo di avere capito.La più generica invocazione di un delegato in un thread specifico, infatti, sembra essere la SynchronizationContext classe.

Primo, l' .NET framework non non fornire un "default" significa semplicemente "inviare" un delegato per qualsiasi thread in modo tale che esso verrà eseguito subito.Ovviamente, questo non può funzionare, perché vorrebbe dire "interrompere" qualunque sia il lavoro che il thread sarebbe fare al momento.Pertanto, l'obiettivo del thread stesso a decidere come, e quando, sarà "ricevere" delegati;che è, questa funzionalità deve essere fornito dal programmatore.

Così un thread di destinazione ha bisogno di un modo di "ricevere" delegati.Questo può essere fatto in molti modi diversi.Un meccanismo semplice è per il thread per sempre un ritorno al loop (chiamiamolo il "ciclo di messaggi") in cui si tratterà di una coda.Lavorerò ciò che è nella coda.Windows nativamente lavori come questo, quando si tratta di UI-cose affini.

Nel seguito verrà illustrato come implementare una coda di messaggi e un SynchronizationContext per questo, come pure un thread con un ciclo di messaggi.Infine, verrà illustrato come richiamare un delegato su quel thread.


Esempio:

Passo 1. Facciamo prima creare un SynchronizationContext classe che verrà utilizzato insieme con l'obiettivo coda di messaggi del thread:

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.
}

Fondamentalmente, questo non fa più l'aggiunta di tutti i delegati che sono passati in via Post per un utente fornito di coda.(Post è il metodo per chiamate asincrone. Send sarebbe sincrona per le chiamate.Sto omettendo l'ultima per ora).

Passo 2. Proviamo ora a scrivere il codice per un thread Z attesa per i delegati d per arrivare:

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);
        }
    }
}

Passo 3. Thread Z deve essere avviato da qualche parte:

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

Passo 4. Infine, torna su un altro thread Un, vogliamo inviare un delegato del thread 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);
}

La cosa bella di questo è che SynchronizationContext funziona, non importa se sei in un'applicazione Windows Form in un'applicazione WPF, o in un multi-threaded applicazione console di propria invenzione.Sia windows form e WPF, fornire e installare adatto SynchronizationContexts per la loro principale/thread UI.

La procedura generale per il richiamo di un delegato in un thread specifico è il seguente:

  • È necessario acquisire il thread di destinazione (ilZ's) SynchronizationContext, in modo che si può Send (sincrono) o Post (asincrono) un delegato per quel thread.Il modo per farlo è quello di memorizzare il contesto di sincronizzazione restituito da SynchronizationContext.Current mentre sei sul thread di destinazione Z.(Questo contesto di sincronizzazione deve avere precedentemente registrati/il filo Z.) Poi archivio di riferimento da qualche parte dove è accessibile dal thread Un.

  • Mentre sul thread Un, è possibile utilizzare il catturati contesto di sincronizzazione per inviare o pubblicare alcun delegato al thread Z: zSyncContext.Post(_ => { ... }, null);

Altri suggerimenti

Se vuoi supportare la chiamata di un delegato su un thread che altrimenti non ha un ciclo di messaggi, devi implementare il tuo, fondamentalmente.

Non c'è niente di particolarmente magico in un ciclo di messaggi: è proprio come un consumatore in un normale modello produttore / consumatore. Mantiene una coda di cose da fare (tipicamente eventi a cui reagire) e passa attraverso la coda agendo di conseguenza. Quando non c'è più niente da fare, aspetta che qualcosa venga messo in coda.

In altre parole: puoi pensare a un thread con un loop di messaggi come un pool di thread a thread singolo.

Puoi implementarlo da solo abbastanza facilmente, anche in un'app console. Ricorda solo che se il thread sta eseguendo un ciclo attorno alla coda di lavoro, non può fare anche qualcos'altro, mentre in genere il thread principale di esecuzione in un'app console ha lo scopo di eseguire una sequenza di attività e quindi finire.

Se utilizzi .NET 4, è molto semplice implementare una coda produttore / consumatore utilizzando BlockingCollection class.

Di recente mi sono imbattuto in questo articolo e l'ho trovato un vero toccasana.L'uso di una coda simultanea di blocco è la salsa segreta, come sottolineato da Jon Skeet sopra.Il miglior "come" che ho trovato per realizzare tutto questo lavoro è questo articolo su CodeProject di Mike Peretz.L'articolo fa parte di una serie in tre parti su SynchronizationContext che fornisce esempi di codice che possono essere facilmente trasformati in codice di produzione.Nota solo Peretz riempie tutti i dettagli, ma ci ricorda anche che il SynchronizationContext di base ha implementazioni essenzialmente inutili di Post () e Send () e quindi dovrebbe essere visto come una classe base astratta.Un utente occasionale della classe base potrebbe essere sorpreso di scoprire che non risolve i problemi del mondo reale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top