it-swarm.com.de

Threadübergreifender Vorgang nicht gültig: Auf das Steuerelement wurde von einem anderen Thread als dem Thread zugegriffen, für den es erstellt wurde

Ich habe ein Szenario. (Windows Forms, C #, .NET)

  1. Es gibt ein Hauptformular, das einige Benutzersteuerelemente enthält.
  2. Das Benutzersteuerelement führt eine umfangreiche Datenoperation aus, sodass die Benutzeroberfläche für die Dauer der Ausführung der Lademethode nicht mehr reagiert, wenn ich die Methode UserControl_Load direkt aufrufe.
  3. Um dies zu überwinden, lade ich Daten auf einen anderen Thread (versuche, vorhandenen Code so wenig wie möglich zu ändern)
  4. Ich habe einen Hintergrund-Worker-Thread verwendet, der die Daten lädt und die Anwendung benachrichtigt, wenn sie ihre Arbeit erledigt hat.
  5. Jetzt kam ein echtes Problem. Die gesamte Benutzeroberfläche (Hauptformular und seine untergeordneten Benutzersteuerelemente) wurde auf dem primären Hauptthread erstellt. In der LOAD-Methode des Benutzersteuerelements rufe ich Daten auf der Grundlage der Werte eines Steuerelements (z. B. eines Textfelds) in userControl ab.

Der Pseudocode würde so aussehen:

CODE 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.
    }
}

Die Ausnahme, die es gab, war

Threadübergreifender Vorgang nicht gültig: Auf das Steuerelement wurde von einem anderen Thread als dem Thread zugegriffen, für den es erstellt wurde.

Um mehr darüber zu erfahren, habe ich ein bisschen gegoogelt und es kam ein Vorschlag wie der folgende Code

CODE 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
    }
}

ABER ABER ABER ... es scheint, ich bin wieder auf dem ersten Platz. Die Anwendung reagiert erneut nicht mehr. Es scheint auf die Ausführung von Zeile 1 zurückzuführen zu sein, wenn Bedingung. Der Ladevorgang wird wieder vom übergeordneten Thread erledigt und nicht vom dritten, den ich erzeugt habe.

Ich weiß nicht, ob ich das richtig oder falsch empfunden habe. Ich bin neu im Threading.

Wie behebe ich das und was bewirkt die Ausführung von Zeile 1, wenn block?

Die Situation ist die folgende: Ich möchte Daten basierend auf dem Wert eines Steuerelements in eine globale Variable laden. Ich möchte den Wert eines Steuerelements aus dem untergeordneten Thread nicht ändern. Ich werde es nie von einem Kind Thread tun.

Es wird also nur auf den Wert zugegriffen, damit die entsprechenden Daten aus der Datenbank abgerufen werden können.

546
Prerak K

Gemäß pdate-Kommentar von Prerak K (seitdem gelöscht):

Ich glaube, ich habe die Frage nicht richtig gestellt.

Die Situation ist wie folgt: Ich möchte Daten basierend auf dem Wert eines Steuerelements in eine globale Variable laden. Ich möchte den Wert eines Steuerelements aus dem untergeordneten Thread nicht ändern. Ich werde es nie von einem Kind Thread tun.

Es wird also nur auf den Wert zugegriffen, damit entsprechende Daten aus der Datenbank abgerufen werden können.

Die gewünschte Lösung sollte dann so aussehen:

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

Führen Sie Ihre ernsthafte Verarbeitung in dem separaten Thread durch, bevor Sie versuchen, zu dem Thread des Steuerelements zurückzukehren. Zum Beispiel:

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;
        }
    }
}
411
Jeff Hubbard

Threading-Modell in der Benutzeroberfläche

Bitte lesen Sie das Threading-Modell in UI-Anwendungen, um grundlegende Konzepte zu verstehen. Der Link navigiert zu einer Seite, die das WPF-Threading-Modell beschreibt. Windows Forms verwendet jedoch dieselbe Idee.

Der UI-Thread

  • Es gibt nur einen Thread (UI-Thread), der auf System.Windows.Forms.Control und seine Unterklassenmitglieder zugreifen darf.
  • Der Versuch, von einem anderen Thread als dem Benutzeroberflächenthread auf das Mitglied von System.Windows.Forms.Control zuzugreifen, führt zu einer Threadübergreifenden Ausnahme.
  • Da es nur einen Thread gibt, werden alle UI-Vorgänge als Arbeitselemente in diesen Thread eingereiht:

enter image description here

enter image description here

BeginInvoke- und Invoke-Methoden

  • Der Rechenaufwand für die aufgerufene Methode sollte gering sein, ebenso wie der Rechenaufwand für Event-Handler-Methoden, da der UI-Thread dort verwendet wird - derselbe, der für die Verarbeitung von Benutzereingaben verantwortlich ist. Unabhängig davon, ob dies System.Windows.Forms.Control.Invoke oder System.Windows.Forms.Control.BeginInvoke ist.
  • Verwenden Sie zur Durchführung eines rechenintensiven Vorgangs immer einen separaten Thread. Seit .NET 2.0 ist BackgroundWorker für die Ausführung rechenintensiver Vorgänge in Windows Forms vorgesehen. In neuen Lösungen sollten Sie jedoch das Async-Warten-Muster verwenden, wie beschrieben hier .
  • Verwenden Sie System.Windows.Forms.Control.Invoke oder System.Windows.Forms.Control.BeginInvoke nur zum Aktualisieren einer Benutzeroberfläche. Wenn Sie sie für umfangreiche Berechnungen verwenden, blockiert Ihre Anwendung Folgendes:

enter image description here

Aufrufen

enter image description here

BeginInvoke

enter image description here

Code-Lösung

Lesen Sie die Antworten auf die Frage Wie aktualisiere ich die GUI von einem anderen Thread in C #? . Für C # 5.0 und .NET 4.5 ist die empfohlene Lösung hier .

165
Ryszard Dżegan

Sie möchten Invoke oder BeginInvoke nur für die minimale Arbeit verwenden, die zum Ändern der Benutzeroberfläche erforderlich ist. Ihre "schwere" Methode sollte auf einem anderen Thread ausgeführt werden (z. B. über BackgroundWorker), dann jedoch Control.Invoke/Control.BeginInvoke verwenden, um nur die Benutzeroberfläche zu aktualisieren. Auf diese Weise kann Ihr UI-Thread UI-Ereignisse usw. verarbeiten.

In meinem Threading-Artikel finden Sie ein WinForms-Beispiel - obwohl der Artikel geschrieben wurde, bevor BackgroundWorker in die Szene kam, und ich befürchte, dass ich ihn in dieser Hinsicht nicht aktualisiert habe. BackgroundWorker vereinfacht lediglich den Rückruf ein wenig.

72
Jon Skeet

Ich hatte dieses Problem mit dem FileSystemWatcher und stellte fest, dass der folgende Code das Problem löste:

fsw.SynchronizingObject = this

Das Steuerelement verwendet dann das aktuelle Formularobjekt, um mit den Ereignissen umzugehen, und befindet sich daher im selben Thread.

41
Peter C

Ich weiß, dass es jetzt zu spät ist. Aber auch heute noch, wenn Sie Probleme haben, auf Cross-Thread-Steuerelemente zuzugreifen? Dies ist die kürzeste Antwort bis zum Datum: P

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

So greife ich über einen Thread auf jedes Formularsteuerelement zu.

31
Bravo

Ich finde den Check-and-Invoke-Code, der in allen Methoden im Zusammenhang mit Formularen verstreut werden muss, viel zu ausführlich und unnötig. Hier ist eine einfache Erweiterungsmethode, mit der Sie sie vollständig umgehen können:

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

Und dann können Sie das einfach tun:

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

Kein Herumspielen mehr - einfach.

17
Rob

Steuerelemente in .NET sind im Allgemeinen nicht threadsicher. Das bedeutet, dass Sie nicht von einem anderen Thread als dem, in dem sich der Thread befindet, auf ein Steuerelement zugreifen sollten. Um dies zu umgehen, müssen Sie aufrufen das Steuerelement, das Ihre 2. Stichprobe versucht.

In Ihrem Fall ist alles, was Sie getan haben, die lang laufende Methode an den Haupt-Thread zurückzugeben. Natürlich ist das nicht wirklich das, was Sie tun möchten. Sie müssen dies ein wenig überdenken, damit Sie im Haupt-Thread nur hier und da eine schnelle Eigenschaft festlegen.

17
Joel Coehoorn

Die sauberste (und richtige) Lösung für Probleme mit dem Cross-Threading der Benutzeroberfläche ist die Verwendung von SynchronizationContext (siehe Synchronisieren von Aufrufen an die Benutzeroberfläche in einer Multithread-Anwendung Artikel, in dem dies sehr gut erklärt wird.

14
Igor Brejc

Ein neuer Look mit Async/Await und Callbacks. Sie benötigen nur eine Codezeile, wenn Sie die Erweiterungsmethode in Ihrem Projekt beibehalten.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

Sie können der Extension-Methode andere Dinge hinzufügen, z. B. das Umschließen in eine Try/Catch-Anweisung, sodass der Aufrufer ihm mitteilen kann, welcher Typ nach Abschluss zurückgegeben werden soll.

Hinzufügen von Try Catch, Auto Exception Logging und CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }
10
John Peters

Sie müssen sich das Backgroundworker-Beispiel ansehen:
http://msdn.Microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Insbesondere wie es mit der Benutzeroberflächenebene interagiert. Basierend auf Ihrem Beitrag scheint dies Ihre Probleme zu beantworten.

8
Pat

Befolgen Sie die (meiner Meinung nach) einfachste Methode, um Objekte aus einem anderen Thread zu ändern:

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;
        }
    }
}
8
Vanderley Maia

Ich habe dies beim Programmieren eines iOS-Phone-Monotouch-App-Controllers in einem Visual Studio Winforms-Prototypprojekt außerhalb von Xamarin Stuidio benötigt. Ich wollte lieber in VS als in Xamarin Studio programmieren und wollte, dass der Controller vollständig vom Telefon-Framework entkoppelt ist. Auf diese Weise wäre die Implementierung für andere Frameworks wie Android und Windows Phone für zukünftige Anwendungen viel einfacher.

Ich wollte eine Lösung, bei der die grafische Benutzeroberfläche auf Ereignisse reagieren kann, ohne sich mit dem Cross-Threading-Umschaltcode hinter jedem Tastenklick herumschlagen zu müssen. Lassen Sie den Klassencontroller im Grunde damit umgehen, um den Client-Code einfach zu halten. Sie könnten möglicherweise viele Ereignisse auf der GUI haben, bei denen es sauberer wäre, als wenn Sie sie an einer Stelle in der Klasse behandeln könnten. Ich bin kein Multi-Theading-Experte, lassen Sie mich wissen, wenn dies fehlerhaft ist.

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

Dem GUI-Formular ist nicht bekannt, dass der Controller asynchrone Tasks ausführt.

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

Dies ist nicht die empfohlene Methode, um diesen Fehler zu beheben. Sie können ihn jedoch schnell unterdrücken. Ich bevorzuge dies für Prototypen oder Demos. hinzufügen

CheckForIllegalCrossThreadCalls = false

im Konstruktor Form1().

7
Özgür

Hier ist eine alternative Möglichkeit, wenn das Objekt, mit dem Sie arbeiten, nicht über Folgendes verfügt

(InvokeRequired)

Dies ist nützlich, wenn Sie mit dem Hauptformular in einer anderen Klasse als dem Hauptformular mit einem Objekt arbeiten, das sich im Hauptformular befindet, jedoch nicht über InvokeRequired verfügt

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

Es funktioniert genauso wie oben, aber es ist ein anderer Ansatz, wenn Sie kein Objekt mit dem Status invokerequired haben, aber Zugriff auf das Hauptformular haben

6
Ashitakalax

So rufen Sie beispielsweise den Text aus einem Steuerelement des UI-Threads ab:

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
5
UrsulRosu

Entspricht den vorherigen Antworten, ist jedoch eine sehr kurze Ergänzung, die es ermöglicht, alle Control-Eigenschaften zu verwenden, ohne dass eine Ausnahme für den Aufruf eines Cross-Threads vorliegt.

Hilfsmethode

/// <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;
}

Beispielnutzung

// 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;
}
5
Mike
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));
5
Hamid Jolany

Gleiche Frage: Wie aktualisiere ich die GUI von einem anderen Thread in C?

Zwei Wege:

  1. Geben Sie den Wert in e.result zurück und verwenden Sie ihn, um den Textfeldwert im backgroundWorker_RunWorkerCompleted-Ereignis festzulegen

  2. Deklarieren Sie eine Variable, um diese Art von Werten in einer separaten Klasse zu speichern (die als Datenhalter fungiert). Erstellen Sie eine statische Instanz dieser Klasse und greifen Sie über einen beliebigen Thread darauf zu.

Beispiel:

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";
    }
}
3
Saurabh

Aktion y; // innerhalb der Klasse deklariert

label1.Invoke (y = () => label1.Text = "text");

0
Antonio Leite

Benutze einfach folgendes:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });
0
Hasan Shouman