E 'opportuno estendere il controllo per fornire costantemente sicuro funzionalità Invoke / BeginInvoke?

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

Domanda

Nel corso della mia manutenzione per un'applicazione più vecchio che mal violato le regole di aggiornamento cross-thread in WinForms, ho creato il seguente metodo di estensione come un modo per risolvere rapidamente le chiamate illegali quando li ho scoperto:

/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of 
/// updater.  False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread.  If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {
        if (!uiElement.IsHandleCreated)
        {
            // Do nothing if the handle isn't created already.  The user's responsible
            // for ensuring that the handle they give us exists.
            return;
        }

        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

Utilizzo di esempio:

this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);

Mi piace come posso sfruttare chiusure di leggere, anche, anche se forceSynchronous deve essere vero in questo caso:

string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);

Non metto in dubbio l'utilità di questo metodo di fissazione Fi illegali in codice legacy, ma per quanto riguarda il nuovo codice?

E 'un buon design per utilizzare questo metodo per aggiornare interfaccia utente in un pezzo di un nuovo software, quando non si può sapere qual è il filo sta tentando di aggiornare l'interfaccia utente, o dovrebbe codice di nuove WinForms in genere contengono una specifica, il metodo dedicato con il Invoke() appropriata impianto idraulico -related per tutti tali aggiornamenti dell'interfaccia utente? (Cercherò di utilizzare le altre tecniche di lavorazione sfondo appropriate primo luogo, naturalmente, per esempio BackgroundWorker.)

È interessante notare che questo non funzionerà per ToolStripItems . Ho da poco scoperto che essi derivano direttamente da invece Componente di controllo . Invece, deve essere utilizzato invoke del ToolStrip contenente.

Follow ai commenti:

Alcuni commenti suggeriscono che:

if (uiElement.InvokeRequired)

dovrebbe essere:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

Si consideri il seguente MSDN documentazione :

  

Questo significa che può InvokeRequired    return false se non è richiesto Invoke   (La chiamata avviene sullo stesso filo),   o se il controllo è stato creato su un   thread diverso ma il controllo del   manico non è ancora stato creato.

     

Nel caso in cui la maniglia del controllo   non è stata ancora creata, si dovrebbe   Non è sufficiente chiamare proprietà, metodi,   o eventi sul controllo. Questo potrebbe   causare la maniglia del controllo di essere   creato sul thread in background,   isolando il controllo su un thread   senza pompa messaggio e rendendo la   applicazione instabile.

     

È possibile proteggersi da questo caso per   anche controllare il valore di   IsHandleCreated quando InvokeRequired   restituisce false su un thread in background.

Se il controllo è stato creato in un thread diverso, ma la maniglia del controllo non è ancora stato creato, InvokeRequired restituisce false. Ciò significa che se i rendimenti InvokeRequired true, IsHandleCreated sarà sempre vero. Test di nuovo è ridondante e non corretta.

È stato utile?

Soluzione

Mi piace l'idea generale, ma lo faccio vedere un problema. E 'importante per elaborare EndInvokes, o si può avere perdite di risorse. Conosco un sacco di gente non crede questo, ma è proprio vero.

Ecco un collegamento a parlarne . Ci sono anche altri.

Ma la risposta principale che ho è: Sì, penso che hai una bella idea qui

.

Altri suggerimenti

È necessario creare Begin e metodi di estensione finale pure. E se si utilizza farmaci generici, è possibile effettuare la chiamata guardare un po 'più bello.

public static class ControlExtensions
{
  public static void InvokeEx<T>(this T @this, Action<T> action)
    where T : Control
  {
    if (@this.InvokeRequired)
    {
      @this.Invoke(action, new object[] { @this });
    }
    else
    {
      if (!@this.IsHandleCreated)
        return;
      if (@this.IsDisposed)
        throw new ObjectDisposedException("@this is disposed.");

      action(@this);
    }
  }

  public static IAsyncResult BeginInvokeEx<T>(this T @this, Action<T> action)
    where T : Control
  {
    return @this.BeginInvoke((Action)delegate { @this.InvokeEx(action); });
  }

  public static void EndInvokeEx<T>(this T @this, IAsyncResult result)
    where T : Control
  {
    @this.EndInvoke(result);
  }
}

Ora le chiamate diventano un po 'più corto e più pulito:

this.lblTimeDisplay.InvokeEx(l => l.Text = this.task.Duration.ToString());

var result = this.BeginInvokeEx(f => f.Text = "Different Title");
// ... wait
this.EndInvokeEx(result);

E per quanto riguarda la Components, basta richiamare la forma o il contenitore stesso.

this.InvokeEx(f => f.toolStripItem1.Text = "Hello World");

Questo non è in realtà una risposta, ma risponde ad alcune osservazioni per la risposta accettata.

Per i modelli IAsyncResult standard, il metodo BeginXXX contiene parametro AsyncCallback, quindi se si vuole dire "Non mi preoccupo di questo - basta chiamare EndInvoke quando è fatto e ignorare il risultato", si può fare qualcosa di simile ( questo è per Action, ma dovrebbe essere in grado di essere regolato per altri tipi delegato):

    ...
    public static void BeginInvokeEx(this Action a){
        a.BeginInvoke(a.EndInvoke, a);
    }
    ...
    // Don't worry about EndInvoke
    // it will be called when finish
    new Action(() => {}).BeginInvokeEx(); 

(Purtroppo non ho una soluzione non avere una funzione di supporto senza dichiarare una variabile ogni volta che uso questo schema).

Ma per Control.BeginInvoke non abbiamo AsyncCallBack, quindi non c'è modo semplice per esprimere questo con Control.EndInvoke garantito di essere chiamato. Il modo in cui è stato progettato richiede il fatto che Control.EndInvoke è facoltativo.

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