c# - net - windows form update ui from another thread




Operazione cross-thread non valida: controllo a cui si accede da un thread diverso dal thread su cui è stato creato (14)

Threading Model in UI

Si prega di leggere il modello di threading nelle applicazioni dell'interfaccia utente al fine di comprendere i concetti di base. Il collegamento passa alla pagina che descrive il modello di threading WPF. Tuttavia, Windows Form utilizza la stessa idea.

Il thread dell'interfaccia utente

  • C'è solo un thread (thread UI), che è autorizzato ad accedere a System.Windows.Forms.Control e ai suoi membri di sottoclassi.
  • Tentativo di accedere al membro di System.Windows.Forms.Control da thread diversi dal thread dell'interfaccia utente causerà un'eccezione cross-thread.
  • Poiché esiste un solo thread, tutte le operazioni dell'interfaccia utente vengono accodate come elementi di lavoro in tale thread:

Metodi BeginInvoke e Invoke

  • Il sovraccarico di calcolo del metodo invocato dovrebbe essere ridotto così come l'overhead di calcolo dei metodi del gestore di eventi, in quanto viene utilizzato il thread dell'interfaccia utente, lo stesso responsabile della gestione dell'input dell'utente. Indipendentemente dal fatto che si tratti di System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke .
  • Per eseguire operazioni di calcolo costose, utilizzare sempre thread separati. Poiché .NET 2.0 BackgroundWorker è dedicato all'elaborazione di operazioni costose in Windows Form. Tuttavia, nelle nuove soluzioni dovresti usare il modello async-await come descritto here .
  • Utilizzare i metodi System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke solo per aggiornare un'interfaccia utente. Se li usi per calcoli pesanti, la tua applicazione bloccherà:

Invocare

BeginInvoke

Soluzione di codice

Leggi le risposte alla domanda Come aggiornare la GUI da un altro thread in C #? . Per C # 5.0 e .NET 4.5 la soluzione consigliata è here .

Ho uno scenario. (Windows Forms, C #, .NET)

  1. C'è una forma principale che ospita alcuni controlli utente.
  2. Il controllo utente esegue alcune operazioni con dati pesanti, in modo tale che se io richiami direttamente il metodo UserControl_Load , l'interfaccia utente non risponde per la durata dell'esecuzione del metodo di caricamento.
  3. Per superare questo carico di dati su thread diversi (cercando di cambiare il codice esistente il meno possibile)
  4. Ho usato un thread di background worker che caricherà i dati e una volta terminato informerà l'applicazione che ha fatto il suo lavoro.
  5. Ora è venuto un vero problema. Tutta la UI (il modulo principale e i relativi comandi secondari figlio) sono stati creati sul thread principale principale. Nel metodo LOAD di usercontrol sto recuperando i dati in base ai valori di alcuni controlli (come la casella di testo) su userControl.

Lo pseudocodice sarebbe simile a questo:

CODICE 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

L'eccezione che ha dato è stata

Operazione cross-thread non valida: controllo a cui si accede da un thread diverso dal thread su cui è stato creato.

Per saperne di più su questo ho fatto qualche ricerca su google e un suggerimento è venuto come usare il seguente codice

CODICE 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

MA MA MA ... sembra che io torni al punto di partenza. L'applicazione nuovamente non risponde. Sembra essere dovuto all'esecuzione della riga n. 1 se condizione. L'attività di caricamento viene di nuovo eseguita dal thread padre e non dal terzo che ho generato.

Non so se ho percepito questo giusto o sbagliato. Sono nuovo al threading.

Come posso risolvere questo e qual è l'effetto dell'esecuzione della riga n. 1 se blocco?

La situazione è questa : voglio caricare i dati in una variabile globale basata sul valore di un controllo. Non voglio cambiare il valore di un controllo dal thread figlio. Non lo farò mai da un thread figlio.

Quindi, solo accedendo al valore in modo che i dati corrispondenti possano essere recuperati dal database.


Ad esempio per ottenere il testo da un controllo del thread dell'interfaccia utente:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
         text = CStr(ctl.Invoke(New GetControlTextInvoker(AddressOf GetControlText), _
                           ctl))
    Else
        text = ctl.Text
    End If

Return text
End Function

Ci sono due opzioni per le operazioni cross thread.

Control.InvokeRequired Property 

e il secondo è da usare

SynchronizationContext Post Method

Control.InvokeRequired è utile solo quando si lavorano i controlli ereditati dalla classe Control mentre SynchronizationContext può essere utilizzato ovunque. Alcune informazioni utili sono i seguenti link

Interfaccia utente aggiornamento discussione incrociata | .Netto

Interfaccia utente aggiornamento thread incrociati con SynchronizationContext | .Netto


Come da commento di aggiornamento di Prerak K (dal cancellato):

Immagino di non aver presentato correttamente la domanda.

La situazione è questa: voglio caricare i dati in una variabile globale basata sul valore di un controllo. Non voglio cambiare il valore di un controllo dal thread figlio. Non lo farò mai da un thread figlio.

Quindi, solo accedendo al valore in modo che i dati corrispondenti possano essere recuperati dal database.

La soluzione che vuoi poi dovrebbe assomigliare a:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Esegui la tua elaborazione seria nel thread separato prima di tentare di tornare alla discussione del controllo. Per esempio:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

Hai bisogno di guardare l'esempio di Backgroundworker:
BackgroundWorker Soprattutto in che modo interagisce con il livello dell'interfaccia utente. In base al tuo post, questo sembra rispondere ai tuoi problemi.


Ho avuto questo problema con FileSystemWatcher e ho scoperto che il seguente codice risolveva il problema:

fsw.SynchronizingObject = this

Il controllo quindi utilizza l'oggetto modulo corrente per gestire gli eventi e quindi si troverà sullo stesso thread.



Ne ho trovato la necessità durante la programmazione di un app monotouch iOS-Phone in un progetto prototipo di winforms dello studio visivo al di fuori di xamarin stuidio. Preferendo programmare il più possibile in VS su xamarin studio, volevo che il controller fosse completamente disaccoppiato dalla struttura del telefono. In questo modo l'implementazione di questo per altri framework come Android e Windows Phone sarebbe molto più semplice per gli usi futuri.

Volevo una soluzione in cui la GUI potesse rispondere agli eventi senza l'onere di gestire il codice di commutazione cross-thread dietro ogni clic del pulsante. Fondamentalmente lascia che il controller di classe gestisca quello per mantenere il codice client semplice. Potresti avere molti eventi sulla GUI dove, come se potessi gestirlo in un posto della classe, sarebbe più pulito. Non sono un esperto multi-via, fammi sapere se questo è difettoso.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

La forma della GUI non è consapevole del fatto che il controllore esegua attività asincrone.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

Seguendo le stesse linee delle risposte precedenti, ma un'aggiunta molto breve che consente di utilizzare tutte le proprietà di controllo senza l'eccezione di richiamo del thread incrociato.

Metodo di supporto

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

Esempio di utilizzo

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}

Segui il modo più semplice (a mio avviso) per modificare oggetti da un altro thread:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

So che è troppo tardi ora. Tuttavia, anche oggi se hai problemi ad accedere ai controlli cross thread? Questa è la risposta più breve fino alla data: P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

Questo è il modo in cui accedo a qualsiasi controllo di modulo da un thread.


Trovo che il codice check-and-call che deve essere disseminato in tutti i metodi relativi ai moduli sia troppo prolisso e non necessario. Ecco un semplice metodo di estensione che ti consente di eliminarlo completamente:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

E quindi puoi semplicemente fare questo:

textbox1.Invoke(t => t.Text = "A");

Non più scherzi: semplice.


La stessa domanda: how-to-update-the-gui-from-another-thread-in-c

Due strade:

  1. Restituisce valore in e.result e lo usa per impostare il valore della tua casella di testo in backgroundWorker_RunWorkerCompleted event

  2. Dichiarare alcune variabili per contenere questo tipo di valori in una classe separata (che funzionerà come titolare dei dati). Crea un'istanza statica di questa classe e puoi accedervi da qualsiasi thread.

Esempio:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));




invoke