Automatizzare il modello di codice InvokeRequired


179

Sono diventato dolorosamente consapevole della frequenza con cui è necessario scrivere il seguente modello di codice nel codice della GUI basato sugli eventi, dove

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

diventa:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Questo è un modello imbarazzante in C #, sia da ricordare, sia da digitare. Qualcuno ha escogitato una sorta di scorciatoia o costrutto che automatizza questo in una certa misura? Sarebbe bello se ci fosse un modo per associare una funzione agli oggetti che fa questo controllo senza dover passare attraverso tutto questo lavoro extra, come un object1.InvokeIfNecessary.visible = truecollegamento di tipo.

Le risposte precedenti hanno discusso dell'impraticabilità di chiamare Invoke () ogni volta, e anche in questo caso la sintassi di Invoke () è sia inefficiente che tuttora scomoda da gestire.

Quindi, qualcuno ha capito delle scorciatoie?


2
Mi sono chiesto la stessa cosa, ma per quanto riguarda Dispatcher.CheckAccess () di WPF.
Taylor Leese

Ho pensato a un suggerimento piuttosto folle ispirato alla tua object1.InvokeIfNecessary.Visible = truebattuta; controlla la mia risposta aggiornata e fammi sapere cosa ne pensi.
Dan Tao

1
Aggiungi uno snippet per aiutare a implementare il metodo suggerito da Matt Davis: vedi la mia risposta (in ritardo ma sto solo mostrando come per i lettori successivi ;-))
Aaron Gage,

3
Non capisco perché Microsoft non abbia fatto nulla per semplificarlo in .NET. La creazione di delegati per ogni modifica nel modulo dal thread è davvero fastidiosa.
Kamil,

@Kamil Non potrei essere più d'accordo! Questa è una tale svista, data la sua ubiquità. All'interno del framework, basta gestire il threading se necessario. Sembra ovvio.
SteveCinq,

Risposte:


138

L'approccio di Lee può essere ulteriormente semplificato

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

E può essere chiamato così

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Non è necessario passare il controllo come parametro al delegato. C # crea automaticamente una chiusura .


AGGIORNAMENTO :

Secondo diversi altri poster si Controlpossono generalizzare come ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott sottolineato che a differenza Controll' ISynchronizeInvokeinterfaccia richiede un array oggetto per il Invokemetodo dei parametri per il action.


AGGIORNAMENTO 2

Modifiche suggerite da Mike de Klerk (vedi commento nel primo frammento di codice per il punto di inserimento):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Vedere il commento di ToolmakerSteve di seguito per dubbi su questo suggerimento.


2
Non sarebbe meglio avere ISynchronizeInvokeinvece Control? (Complimenti a Jon Skeet stackoverflow.com/questions/711408/… )
Odys

@odyodyodys: buon punto. Non lo sapevo ISynchronizeInvoke. Ma l'unico tipo che ne deriva (secondo Reflector) è Control, quindi l'avavantage è limitato.
Olivier Jacot-Descombes,

3
@ mike-de-clerk, sono preoccupato per il tuo suggerimento da aggiungere while (!control.Visible) ..sleep... Per me ha un cattivo odore di codice, in quanto è un ritardo potenzialmente illimitato (forse anche un ciclo infinito in alcuni casi), nel codice che può avere chiamanti che non si aspettano un tale ritardo (o anche un deadlock). IMHO, qualsiasi utilizzo di Sleepdovrebbe essere la responsabilità di ogni chiamante, O dovrebbe essere in un wrapper separato che è chiaramente contrassegnato sulle sue conseguenze. IMHO, di solito sarebbe meglio "fallire duramente" (eccezione, catturare durante i test) o "non fare nulla" se il controllo non è pronto. Commenti?
ToolmakerSteve

1
@ OlivierJacot-Descombes, Sarebbe bello, per favore, spiegarmi come funziona thread.invokerequired?
Sudhir.net,

1
InvokeRequiredindica se il thread chiamante è diverso dal thread che ha creato il controllo. Invokepassa l'azione dal thread chiamante al thread del controllo in cui viene eseguito. Ciò garantisce che, ad esempio, un gestore di eventi click non venga mai interrotto.
Olivier Jacot-Descombes,

133

È possibile scrivere un metodo di estensione:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

E usalo in questo modo:

object1.InvokeIfRequired(c => { c.Visible = true; });

MODIFICA: Come sottolinea Simpzon nei commenti, puoi anche cambiare la firma in:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Forse sono troppo stupido, ma questo codice non verrà compilato. Quindi l'ho risolto come costruito da me (VS2008).
Oliver,

5
Solo per completezza: in WPF esiste un meccanismo di dispacciamento diverso, ma funziona in modo analogo. È possibile utilizzare questo metodo di estensione lì: public static void InvokeIfRequired <T> (this T aTarget, Action <T> aActionToExecute) dove T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Ho aggiunto una risposta che semplifica leggermente la soluzione di Lee.
Olivier Jacot-Descombes,

Ciao, visto che utilizzo qualcosa di simile, potrebbe esserci un grosso problema derivante da questa implementazione generica. Se il controllo è in smaltimento / eliminato, si otterrà un ObjectDisposedException.
Offler,

1
@Offler - Beh, se vengono disposti su un thread diverso hai un problema di sincronizzazione, non è un problema in questo metodo.
Lee

33

Ecco il modulo che ho usato in tutto il mio codice.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

Ho basato questo sul post di blog qui . Non ho fallito questo approccio, quindi non vedo alcun motivo per complicare il mio codice con un controllo della InvokeRequiredproprietà.

Spero che questo ti aiuti.


+1 - Mi sono imbattuto nello stesso post di blog che hai fatto e penso che questo sia l'approccio più pulito di qualsiasi proposta
Tom Bushell,

3
C'è un piccolo impatto sulle prestazioni usando questo approccio, che potrebbe accumularsi quando chiamato più volte. stackoverflow.com/a/747218/724944
surfen,

4
Devi utilizzare InvokeRequiredse il codice può essere eseguito prima che il controllo sia stato mostrato o avrai un'eccezione fatale.
56ka,

9

Crea un file ThreadSafeInvoke.snippet, quindi puoi semplicemente selezionare le istruzioni di aggiornamento, fare clic con il tasto destro e selezionare 'Surround With ...' o Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

6

Ecco una versione migliorata / combinata delle risposte di Lee, Oliver e Stephan.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

Il modello consente un codice flessibile e senza cast che è molto più leggibile mentre il delegato dedicato fornisce efficienza.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

4

Preferirei utilizzare una singola istanza di un metodo Delegate invece di creare una nuova istanza ogni volta. Nel mio caso ho usato per mostrare i progressi e (informazioni / errori) messaggi da un Backroundworker copiando e trasmettendo dati di grandi dimensioni da un'istanza sql. Ogni volta dopo circa 70000 progressi e chiamate di messaggi il mio modulo ha smesso di funzionare e mostrando nuovi messaggi. Questo non si è verificato quando ho iniziato a utilizzare un singolo delegato di istanza globale.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

3

Uso:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Codice:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

2

Mi piacerebbe farlo un po 'diverso, mi piace chiamare "me stesso" se necessario con un'azione,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

questo è un modello utile, IsFormClosing è un campo che ho impostato su True quando sto chiudendo il mio modulo in quanto potrebbero esserci alcuni thread in background che sono ancora in esecuzione ...


-3

Non dovresti mai scrivere codice simile al seguente:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Se hai un codice simile a questo, la tua applicazione non è thread-safe. Significa che hai un codice che sta già chiamando DoGUISwitch () da un thread diverso. È troppo tardi per verificare se si trova in un thread diverso. InvokeRequire deve essere chiamato PRIMA di effettuare una chiamata a DoGUISwitch. Non dovresti accedere a nessun metodo o proprietà da un thread diverso.

Riferimento: Proprietà Control.InvokeRequired in cui è possibile leggere quanto segue:

Oltre alla proprietà InvokeRequired, su un controllo sono disponibili quattro metodi che è possibile chiamare con thread thread: Invoke, BeginInvoke, EndInvoke e CreateGraphics se l'handle per il controllo è già stato creato.

In una singola architettura CPU non ci sono problemi, ma in un'architettura multi-CPU è possibile assegnare parte del thread dell'interfaccia utente al processore in cui era in esecuzione il codice chiamante ... e se tale processore è diverso da dove il thread dell'interfaccia utente era in esecuzione quindi quando termina il thread chiamante Windows penserà che il thread dell'interfaccia utente sia terminato e ucciderà il processo dell'applicazione, ovvero l'applicazione verrà chiusa senza errori.


Ehi, grazie per la risposta. Sono passati anni da quando ho posto questa domanda (e quasi altrettanto tempo da quando ho lavorato con C #), ma mi chiedevo se potessi spiegare un po 'di più? I documenti a cui ti sei collegato fanno riferimento a un pericolo specifico di chiamare invoke()et al prima che al controllo venga assegnato un handle, ma IMHO non descrive ciò che hai descritto. Il punto centrale di tutte queste invoke()sciocchezze è aggiornare l'interfaccia utente in modo thread-safe, e penso che mettere più istruzioni in un contesto di blocco porterebbe a balbettare? (Ugh ... felice di aver smesso di usare la tecnologia M $. Così complicato!)
Tom Corelis,

Voglio anche notare che nonostante l'uso frequente del codice originale (molto tempo fa), non ho osservato il problema che hai descritto sul mio desktop a doppia CPU
Tom Corelis,

3
Dubito che questa risposta sia accurata poiché MSDN mostra molti esempi proprio come l'OP ha dato.
wireless pubblico
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.