Threading C # Winform: il modulo chiuso viene richiamato

Il seguente codice dimostra il mio dilemma. Il codice crea un thread in background che elabora qualcosa, quindi Invoca il thread dell’interfaccia utente con il risultato.

Può generare un’eccezione se il thread in background richiama Invoke sul modulo dopo che il modulo è stato chiuso. Controlla IsHandleCreated prima di chiamare Invoke, ma il modulo potrebbe chiudersi dopo il controllo.

void MyMethod() { // Define background thread Action action = new Action( () => { // Process something var data = BackgroundProcess(); // Try to ensure the form still exists and hope // that doesn't change before Invoke is called if (!IsHandleCreated) return; // Send data to UI thread for processing Invoke(new MethodInvoker( () => { UpdateUI(data); })); }); // Queue background thread for execution action.BeginInvoke(); } 

Una soluzione potrebbe essere sincronizzare FormClosing e ogni chiamata a Invoke, ma ciò non sembra molto elegante. C’è un modo più semplice?

    Sì, c’è una gara qui. A impiega un buon millisecondo prima che il bersaglio inizi a correre. Funzionerà “meglio” se si utilizza Control.BeginInvoke () invece, l’implementazione di Dispose () del modulo svuoterà la coda di invio. Ma questa è ancora una gara, anche se colpirà molto raramente. Il tuo codice come scritto nello snippet non richiede Invoke ().

    L’unica soluzione pulita consiste nell’interbloccare l’evento FormClosing e ritardare la chiusura fino a quando non viene confermata la conclusione del thread in background e non è ansible riavviarlo. Non è facile fare con il tuo codice perché richiede un callback ‘completato’ in modo da poter davvero chiudere il modulo. BackgroundWorker sarebbe una trappola per topi migliore . La correzione Q & D è di catturare l’ObjectDisposedException che BeginInvoke solleverà. Considerata la rarità di ciò quando si utilizza BeginInvoke (), quel brutto attacco potrebbe essere accettabile. Non puoi testarlo 🙂

    Ho risolto questo problema di sincronizzazione per BeginInvoke utilizzando il suggerimento di Hans Passant per rilevare ObjectDisposedException. Finora, sembra funzionare. Ho creato metodi di estensione della class Control per facilitare questo.

    TryBeginInvoke tenta di richiamare il proprio metodo sul controllo. Se il metodo viene richiamato correttamente, controlla se il controllo è stato eliminato. Se è stato smaltito, ritorna immediatamente; in caso contrario, chiama il metodo originariamente passato come parametro a TryBeginInvoke. Il codice è il seguente:

     public static class ControlExtension { // --- Static Fields --- static bool _fieldsInitialized = false; static InvokeDelegateDelegate _methodInvokeDelegate; // Initialized lazily to reduce application startup overhead [see method: InitStaticFields] static InvokeMethodDelegate _methodInvokeMethod; // Initialized lazily to reduce application startup overhead [see method: InitStaticFields] // --- Public Static Methods --- public static bool TryBeginInvoke(this Control control, Delegate method, params object[] args) { IAsyncResult asyncResult; return TryBeginInvoke(control, method, out asyncResult, args); } /// May return true even if the target of the invocation cannot execute due to being disposed during invocation. public static bool TryBeginInvoke(this Control control, Delegate method, out IAsyncResult asyncResult, params object[] args) { if (!_fieldsInitialized) InitStaticFields(); asyncResult = null; if (!control.IsHandleCreated || control.IsDisposed) return false; try { control.BeginInvoke(_methodInvokeDelegate, control, method, args); } catch (ObjectDisposedException) { return false; } catch (InvalidOperationException) // Handle not created { return false; } return true; } public static bool TryBeginInvoke(this Control control, MethodInvoker method) { IAsyncResult asyncResult; return TryBeginInvoke(control, method, out asyncResult); } /// May return true even if the target of the invocation cannot execute due to being disposed during invocation. public static bool TryBeginInvoke(this Control control, MethodInvoker method, out IAsyncResult asyncResult) { if (!_fieldsInitialized) InitStaticFields(); asyncResult = null; if (!control.IsHandleCreated || control.IsDisposed) return false; try { control.BeginInvoke(_methodInvokeMethod, control, method); } catch (ObjectDisposedException) { return false; } catch (InvalidOperationException) // Handle not created { return false; } return true; } // --- Private Static Methods --- private static void InitStaticFields() { _methodInvokeDelegate = new InvokeDelegateDelegate(InvokeDelegate); _methodInvokeMethod = new InvokeMethodDelegate(InvokeMethod); } private static object InvokeDelegate(Control control, Delegate method, object[] args) { if (!control.IsHandleCreated || control.IsDisposed) return null; return method.DynamicInvoke(args); } private static void InvokeMethod(Control control, MethodInvoker method) { if (!control.IsHandleCreated || control.IsDisposed) return; method(); } // --- Private Nested Types --- delegate object InvokeDelegateDelegate(Control control, Delegate method, object[] args); delegate void InvokeMethodDelegate(Control control, MethodInvoker method); } 

    Dai un’occhiata a WindowsFormsSynchronizationContext . Il metodo Post post chiama il delegato UpdateUI sul thread dell’interfaccia utente senza bisogno di una finestra dedicata; questo ti consente di saltare la chiamata a IsHandleCreated e Invoke .

    Modifica: MSDN ha alcuni esempi di codice in “Programmazione multithread con pattern asincrono basato su eventi” .

    Potrebbe essere più semplice programmare tramite la class AsyncOperationManager , che si trova in cima a WindowsFormsSynchronizationContext . A sua volta, il componente BackgroundWorker è costruito su AsyncOperationManager .

    Il thread dell’interfaccia utente è definito come quello su cui si chiama AsyncOperationManager.CreateOperation ; vuoi chiamare CreateOperation all’inizio di MyMethod , quando sai di essere sul thread dell’interfaccia utente e acquisirne il valore di ritorno in una variabile locale.

    Puoi verificare IsDisposed sul modulo (o qualsiasi controllo) prima di richiamarlo.

    Dovresti anche controllare questo all’interno del metodo effettivo che stai invocando, nel caso in cui il modulo sia stato disposto nel frattempo.