2010-08-25 18 views
5

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.Threading di # C# Win: il modulo chiuso viene richiamato

Può generare un'eccezione se il thread in background chiama 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 quella di sincronizzare FormClosing e ogni chiamata a Invoke, ma che non suona molto elegante. C'è un modo più semplice?

risposta

4

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 correzione è l'interblocco dell'evento FormClosing e il ritardo alla chiusura finché non viene confermata la conclusione del thread in background e non è possibile riavviarlo. Non è facile fare con il tuo codice perché richiede un callback 'completato' in modo da poter davvero chiudere il modulo. BackgroundWorker would be a better mousetrap. La correzione D & D consiste nel rilevare l'ObjectDisposedException che verrà generato da BeginInvoke. Considerata la rarità di ciò quando si utilizza BeginInvoke(), quel brutto attacco potrebbe essere accettabile. Non è possibile testarlo :)

+0

Ciò potrebbe almeno attenuarlo. Il mio codice come scritto non richiede Invoke? Queste eccezioni vengono lanciate circa 1 su 10 volte. Posso testarli! :) – drifter

+0

Non è necessario, non è necessario ritardare la discussione e attendere che il delegato termini l'esecuzione. Non stai facendo nulla * dopo * la chiamata Invoke. Non puoi testarlo davvero una volta che usi BeginInvoke, dovrai aprire e chiudere il tuo modulo almeno un milione di volte. Ti assicuriamo che la gara è ancora lì, dovrai prendere ODE. –

+0

Non mi dispiacerebbe prendere l'ObjectDisposedException, ma a volte getta InvalidOperationException invece ("Invoke o BeginInvoke non può essere chiamato su un controllo finché non viene creato l'handle della finestra.") Non riesco a capirlo a meno che non riesca a distinguerlo dagli altri InvalidOperationException nel codice invocato, giusto? – drifter

0

È possibile controllare IsDisposed sul modulo (o qualsiasi controllo) prima di richiamare su di esso.

Si dovrebbe anche controllare questo all'interno del metodo effettivo che si sta invocando, nel caso in cui il modulo è stato disposto nel frattempo.

+1

Il problema è che in alcuni punti Invoke lancia ObjectDisposedException anche se controlla IsDisposed immediatamente prima della chiamata. – drifter

1

Dai uno sguardo allo WindowsFormsSynchronizationContext. I post dei metodi Post chiamano al delegato UpdateUI sul thread dell'interfaccia utente senza che sia necessaria una finestra dedicata; questo ti consente di saltare chiamando IsHandleCreated e Invoke.

Modifica: MSDN ha alcuni esempi di codice sotto "Multithreaded Programming with the Event-based Asynchronous Pattern".

Potrebbe risultare più semplice programmare tramite la classe AsyncOperationManager, che si trova sopra WindowsFormsSynchronizationContext. A sua volta, il componente BackgroundWorker si basa su AsyncOperationManager.

Il thread dell'interfaccia utente è definito come quello sul quale si chiama AsyncOperationManager.CreateOperation; si desidera chiamare CreateOperation all'inizio di MyMethod, quando si sa di essere sul thread dell'interfaccia utente e di acquisire il valore restituito in una variabile locale.

+0

Mi piace molto. Alcuni dei miei utenti sono testardi per l'aggiornamento a .NET 4, ma un caso sta crescendo a suo favore. – drifter

+0

Questa è una classe .NET 2.0. Non risolve il problema, Control.Invoke lo utilizza già. Post() genererà ObjectDisposedException. –

+0

Ma i messaggi di 'WindowsFormsSynchronizationContext' tramite un controllo nascosto che viene chiuso dopo che il modulo è stato chiuso e appena prima che il programma venga chiuso. Il tuo modulo non è affatto coinvolto. –

2

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

    /// <remarks>May return true even if the target of the invocation cannot execute due to being disposed during invocation.</remarks> 
    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); 
    } 

    /// <remarks>May return true even if the target of the invocation cannot execute due to being disposed during invocation.</remarks> 
    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); 
}