2012-06-11 14 views
7

Ho una libreria C# che vorrebbe avere la possibilità di inviare/inviare messaggi al thread dell'interfaccia utente "principale" (se ne esiste uno). Questa libreria può essere utilizzato da:Cattura il thread principale SynchronizationContext o Dispatcher da una libreria

  • Un'applicazione WinForms
  • Un'applicazione nativa (con UI)
  • un'applicazione console (senza interfaccia utente)

Nella biblioteca che avevo piace catturare qualcosa (A SynchronizationContext, un Dispatcher, un Task Scheduler, o qualcos'altro) durante l'inizializzazione, che mi permetterà di (in un secondo momento) Invia/Invia lavoro al thread principale (se il thread principale ha quell'abilità- -ha ha un messaggio pompa). Ad esempio, la libreria vorrebbe mettere l'interfaccia utente di Winforms sul thread principale se e solo se l'applicazione principale ha la possibilità per me di accedere al thread principale.

Le cose che ho provato:

  1. A SynchronizationContext:. Catturare questo funziona bene per un'applicazione WinForms (un WindowsFormsSynchronizationContext verrà installato come Current SynchronizationContext questo funziona bene anche per la console app - dal Posso rilevare che Current SynchronizationContext è null (e quindi, so che non ho la possibilità di inviare/inviare lavoro al thread principale.) Il problema qui è l'applicazione UI nativa: ha l'abilità (cioè ha un messaggio pump), ma il contesto Current Synchronization è nullo e quindi non posso distinguerlo dal caso dell'app Console. Se potessi differenziare, potrei semplicemente installa un WindowsFormsSynchronizationContext sul thread principale e sono a posto.
  2. A Dispatcher: Catturare questo utilizzando Current crea un nuovo SynchronizationContext. Quindi, in tutte le situazioni tornerò su un Dispatcher. Tuttavia, per un'app Console, l'utilizzo di Dispatcher.Invoke da un thread in background verrà interrotto (come previsto). Potrei usare Dispatcher.FromThread (che non crea un Dispatcher per il thread se uno non esiste). Ma l'applicazione di interfaccia utente nativa restituirà un Dispatcher nullo con questo metodo e quindi, di nuovo, non riuscirò a distinguere l'applicazione dell'interfaccia utente dall'applicazione console.
  3. A TaskScheduler: Potrei usare FromCurrentSynchronizationContext. Questo ha gli stessi problemi di SynchronizationContext. Cioè Prima di chiamare FromCurrentSyncronizationContext, dovrei controllare se Current SynchronizationContext è nullo (che sarà il caso per l'app Console e l'applicazione ui nativa). Quindi, ancora una volta non riesco a distinguere l'applicazione ui nativa dall'applicazione console.

Io, naturalmente, potrei avere l'utente della mia biblioteca specificare se si tratta di un'applicazione UI quando chiamano il mio metodo Initialize, ma speravo di evitare che complicazioni per l'utente della biblioteca, se possibile .

+0

Sto usando la libreria C# in MFC. Questa libreria ha una chiamata TaskScheduler.FromCurrentSynchronizationContext(). quando eseguo l'applicazione MFC genera un'eccezione dicendo che "l'attuale SynchronizationContext non può essere usato come un TaskScheduler". Qualche idea su come affrontarla?Non sto utilizzando direttamente la libreria C# in MFC, ma ho creato un wrapper nel C++ gestito e sto usando questo wrapper nell'applicazione MFC. –

risposta

5

Questo non è in generale possibile, una libreria che è adatta per essere utilizzata nei thread non può fare alcuna ipotesi su quale particolare thread è il thread dell'interfaccia utente. È possibile acquisire Synchronization.Current ma funzionerà correttamente solo se il metodo di inizializzazione viene chiamato dal thread dell'interfaccia utente. Ciò non è particolarmente inusuale per funzionare bene, come TaskScheduler.FromCurrentSynchronizationContext() tende a funzionare per errore, ma non è una garanzia. È possibile aggiungere un controllo, se Thread.CurrentThread.GetApartmentState() non restituisce STA, quindi le probabilità che non si venga chiamati dal thread dell'interfaccia utente sono molto alte. In questo caso, anche SynchronizationContext.Current sarà nullo, un altro modo di controllare.

I modi (discutibilmente) migliori sono quelli di non preoccuparsi di questo e lasciare che il codice client lo capisca, non avrà alcun problema nel marshalling del callback. O per esporre una proprietà di tipo SynchronizationContext in modo che il codice client possa assegnarlo. Oppure aggiungilo come argomento del costruttore. Getta una InvalidOperationException se sei pronto per Post ma scopri che è ancora nullo, è una svista che il programmatore del client fa solo una volta.

1

Penso che il sia un'opzione per il tuo metodo Initialize (o in qualche modo consentire al chiamante di richiedere l'interazione dell'interfaccia utente), per me è solo più sensato. Non conosco le specifiche ma queste mi sembrano una cosa "cortese" da fare, lascia che il tuo interlocutore decida se vuole o vuole supportare la tua interfaccia utente. Vorrei fare un ulteriore passo avanti e anche come il chiamante per fornire un contesto di sincronizzazione. Ma questa è la mia opinione.

Per rispondere alla tua domanda, ci sono alcuni "hack" che puoi usare per determinare se stai utilizzando un'applicazione per console.Questa domanda SO ha alcune informazioni su questo: C#/.NET: Detect whether program is being run as a service or a console application

+0

Grazie. Per un'applicazione MFC nativa che cosa dovrebbero fornire SynchronizationContext? Un WindowsFormsSynchronizationContext funzionerebbe bene, ma sarebbe un po 'strano per loro fornirlo. –

+1

Dovrei fare un po 'di ricerche o consultare qualcuno che ha più familiarità con MFC, è stato un tempo _ davvero_ da quando ho lavorato con MFC quindi non ne sono nemmeno sicuro. Il mio punto era che non sono un grande fan di "cottura" in questo tipo di scelte, apertura e flessibilità di solito si rivela essere una scelta migliore. – CodingGorilla

0

Modificare l'inizializzazione della libreria per disporre di un parametro SyncronizationContext. Se il parametro è nullo, la libreria non ha bisogno di fare nulla di speciale, se non ci sono aggiornamenti Post/Send della GUI null.

-1

Penso che questo sia esattamente ciò che è AsyncOperationManager.CreateOperation(). “Implementing the Event-based Asynchronous Pattern” stati:

Il modello asincrono basato su eventi fornisce un modo standardizzato per confezionare una classe che ha caratteristiche asincrone. Se implementato con classi helper come AsyncOperationManager, la classe funzionerà correttamente in qualsiasi modello di applicazione, incluse le applicazioni ASP.NET, Console e Windows Form.

Spetta al chiamante decidere se desiderano chiamare la propria API sul thread dell'interfaccia utente oppure no. Se lo fanno, questo catturerà il contesto e gli eventi passeranno attraverso il pump dei messaggi in ordine. In un'applicazione Console è possibile ottenere lo stesso comportamento se si installa un SynchronizationContext come si ottiene gratuitamente utilizzando AsyncContext.Run() dal pacchetto nuget Nito.AsyncEx. Non c'è bisogno di una proprietà aggiuntiva o di dover scrivere il codice condizionale da soli. Se non è disponibile il contesto di sincronizzazione serializzazione, AsyncOperation.Post() utilizzerà il contesto di sincronizzazione falso disponibile per le app della console che invece accodano l'evento al threadpool (ovvero che i post potrebbero non essere eseguiti in ordine). Ricordati di chiamare AsyncOperation.OperationCompleted() o AsyncOperation.PostOperationCompleted() quando hai finito.

Nella biblioteca mi piacerebbe cogliere qualcosa (A SynchronizationContext, un Dispatcher, una Task Scheduler, o altro) durante l'inizializzazione

Questo è esattamente ciò che AsyncOperationManager.CreateOperation() fa, e in un ambiente - modoagnostico Ma dovresti provare ad accoppiare questo con una chiamata a OperationCompleted() che forse sarebbe più difficile data l'API che vuoi esporre. Il modo più semplice per utilizzare AsyncOperation è avviare un'operazione quando la libreria avvia effettivamente un'operazione anziché durante l'inizializzazione. O facendo in modo che la routine di inizializzazione restituisca un handle dell'oggetto contesto IDisposable che segnali al consumatore che devono gestirne la durata.

Problemi correlati