Cross-thread-bewerking niet geldig: besturing toegankelijk vanuit een andere thread dan de thread waarop deze is gemaakt

Ik heb een scenario. (Windows Forms, C#, .NET)

  1. Er is een hoofdformulier dat enige gebruikerscontrole bevat.
  2. De gebruikersbesturing voert een aantal zware gegevensbewerkingen uit, zodat als ik de methode UserControl_Loadrechtstreeks aanroep, de gebruikersinterface niet meer reageert gedurende de duur van de uitvoering van de laadmethode.
  3. Om dit te verhelpen laad ik gegevens op een andere thread (probeert de bestaande code zo min mogelijk te veranderen)
  4. Ik heb een achtergrondwerkthread gebruikt die de gegevens zal laden en wanneer dit klaar is, wordt de toepassing ervan op de hoogte gebracht dat het zijn werk heeft gedaan.
  5. Nu kwam er een echt probleem. Alle gebruikersinterface (hoofdformulier en de onderliggende gebruikerscontroles) is gemaakt op de primaire hoofdthread. In de LOAD-methode van de usercontrol haal ik gegevens op op basis van de waarden van een bepaald besturingselement (zoals een tekstvak) op userControl.

De pseudocode ziet er als volgt uit:

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

De uitzondering die het gaf was

Cross-thread-bewerking niet geldig: toegang tot een andere thread dan de thread waarop deze is gemaakt.

Om hier meer over te weten heb ik wat gegoogled en er kwam een suggestie zoals het gebruik van de volgende 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
    }
}

Maar maar maar … het lijkt erop dat ik een vierkant bereikte. De applicatie opnieuw
Word niet-responsief. Het lijkt te wijten aan de uitvoering van lijn # 1 als conditie. De laadtaak wordt opnieuw gedaan door de bovenliggende thread en niet de derde die ik heb voortgekomen.

Ik weet niet of ik dit recht of fout ervaar. Ik ben nieuw om te rekenen.

Hoe kan ik dit oplossen en ook wat is het effect van de uitvoering van lijn # 1 als blok?

De situatie is dit : ik wil gegevens in een globale variabele laden op basis van de waarde van een controle. Ik wil de waarde van een controle van de schooldraad niet wijzigen. Ik ga het nooit doen van een kinddraad.

Dus alleen toegang tot de waarde, zodat de bijbehorende gegevens kunnen worden opgehaald in de database.


Antwoord 1, Autoriteit 100%

Zoals per Prerak K’s update commentaar (sinds verwijderd):

Ik denk dat ik de vraag niet goed heb gepresenteerd.

Situatie is dit: ik wil gegevens in een globale variabele laden op basis van de waarde van een besturing. Ik wil de waarde van een controle van de schooldraad niet wijzigen. Ik ga het niet doen van een kinddraad.

Dus alleen toegang tot de waarde, zodat corresponderende gegevens kunnen worden opgehaald in de database.

De u die u wilt, moet eruit zien:

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

Doe uw serieuze verwerking in de aparte thread voordatu probeert terug te schakelen naar de thread van het besturingselement. Bijvoorbeeld:

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

Antwoord 2, autoriteit 42%

Threading-model in gebruikersinterface

Lees het Threading-modelin UI-toepassingen (oude VB-link is hier) om basisconcepten te begrijpen. De link navigeert naar de pagina die het WPF-threadingmodel beschrijft. Windows Forms gebruikt echter hetzelfde idee.

De UI-thread

  • Er is maar één thread (UI-thread), die toegang heeft tot System.Windows.Forms.Controlen de leden van zijn subklassen.
  • Poging om toegang te krijgen tot lid van System.Windows.Forms .Controlevan een andere thread dan de UI-thread veroorzaakt een uitzondering voor meerdere threads.
  • Omdat er maar één thread is, worden alle UI-bewerkingen in de wachtrij geplaatst als werkitems in die thread:

BeginInvoke en Invoke-methoden

BOEK

BeginVoke

CODE OPLOSSING

Lees antwoorden op vraag Hoe de GUI bij te werken van een andere draad in C #? .
Voor C# 5.0 en .NET 4.5 is de aanbevolen oplossing hier.


Antwoord 3, autoriteit 16%

U wilt Invokeof BeginInvokealleen gebruiken voor het absolute minimum dat nodig is om de gebruikersinterface te wijzigen. Uw “zware” methode zou moeten worden uitgevoerd op een andere thread (bijv. via BackgroundWorker) maar dan met behulp van Control.Invoke/Control.BeginInvokealleen om de gebruikersinterface. Op die manier is uw UI-thread vrij om UI-gebeurtenissen enz. af te handelen.

Zie mijn threading-artikelvoor een WinForms-voorbeeld– hoewel het artikel is geschreven voordat BackgroundWorkerter plaatse kwam, en ik Ik ben bang dat ik het in dat opzicht niet heb bijgewerkt. BackgroundWorkervereenvoudigt alleen het terugbellen een beetje.


Antwoord 4, autoriteit 12%

Ik weet dat het nu te laat is. Maar zelfs vandaag als u problemen ondervindt bij het verkrijgen van toegang tot cross-thread-besturingselementen? Dit is het kortste antwoord tot nu toe 😛

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

Zo krijg ik toegang tot elk formulierbesturingselement vanuit een thread.


Antwoord 5, autoriteit 10%

Ik heb dit probleem gehad met de FileSystemWatcheren ontdekte dat de volgende code het probleem oploste:

fsw.SynchronizingObject = this

Het besturingselement gebruikt dan het huidige formulierobject om de gebeurtenissen af te handelen en zal daarom in dezelfde thread zitten.


Antwoord 6, autoriteit 5%

Ik vind de check-and-invoke-code die in alle methoden met betrekking tot formulieren moet staan, veel te uitgebreid en onnodig. Hier is een eenvoudige uitbreidingsmethode waarmee je het helemaal kunt afschaffen:

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

En dan kun je gewoon dit doen:

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

Geen geknoei meer – eenvoudig.


Antwoord 7, autoriteit 4%

Besturingselementen in .NET zijn over het algemeen niet threadveilig. Dat betekent dat u geen toegang moet krijgen tot een besturingselement vanuit een andere thread dan die waar het zich bevindt. Om dit te omzeilen, moet je het besturingselement aanroepen, wat je tweede voorbeeld probeert.

In jouw geval heb je echter alleen de langlopende methode doorgegeven aan de hoofdthread. Dat is natuurlijk niet echt wat je wilt doen. Je moet dit een beetje heroverwegen, zodat alles wat je in de hoofdthread doet, hier en daar een snelle eigenschap is.


Antwoord 8, autoriteit 3%

De schoonste (en juiste) oplossing voor problemen met cross-threading van de gebruikersinterface is het gebruik van SynchronizationContext, zie Synchroniseren van oproepen naar de UI in een multi-threaded applicatieartikel, het legt het heel mooi uit.


Antwoord 9, autoriteit 2%

Een nieuwe look met Async/Await en callbacks. U hebt slechts één regel code nodig als u de extensiemethode in uw project behoudt.

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

U kunt andere dingen toevoegen aan de Extension-methode, zoals het inpakken in een Try/Catch-instructie, zodat de beller kan vertellen welk type moet worden geretourneerd na voltooiing, een uitzonderingscallback naar de beller:

Try Catch, Auto Exception Logging en CallBack toevoegen

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

Antwoord 10, autoriteit 2%

Dit is niet de aanbevolen manier om deze fout op te lossen, maar je kunt het snel onderdrukken, het zal het werk doen. Ik geef de voorkeur aan dit voor prototypes of demo’s. voeg

. toe

CheckForIllegalCrossThreadCalls = false

in Form1()constructor .


Antwoord 11, autoriteit 2%

Volg de eenvoudigste (naar mijn mening) manier om objecten uit een andere thread te wijzigen:

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

Antwoord 12, autoriteit 2%

Je moet naar het Backgroundworker-voorbeeld kijken:
http://msdn.microsoft.com/en-us/library /system.componentmodel.backgroundworker.aspx
Vooral hoe het samenwerkt met de UI-laag. Op basis van je bericht lijkt dit je problemen op te lossen.


Antwoord 13, autoriteit 2%

Ik vond hier behoefte aan tijdens het programmeren van een iOS-Phone monotouch app-controller in een visual studio winforms prototypeproject buiten xamarin stuidio. Ik gaf er de voorkeur aan om zoveel mogelijk in VS boven xamarin studio te programmeren, ik wilde dat de controller volledig losgekoppeld zou zijn van het telefoonframework. Op deze manier zou het veel gemakkelijker zijn om dit voor andere frameworks zoals Android en Windows Phone te implementeren voor toekomstig gebruik.

Ik wilde een oplossing waarbij de GUI kon reageren op gebeurtenissen zonder de last van de cross-threading-schakelcode achter elke klik op een knop. Laat de klassencontroller dat in principe afhandelen om de clientcode eenvoudig te houden. Je zou mogelijk veel gebeurtenissen in de GUI kunnen hebben, alsof je het op één plek in de klas zou kunnen doen, schoner zou zijn. Ik ben geen expert op het gebied van multi-theading, laat het me weten als dit niet klopt.

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

Het GUI-formulier weet niet dat de controller asynchrone taken uitvoert.

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

Antwoord 14

Hier is een alternatieve manier als het object waarmee u werkt geen

. heeft

(InvokeRequired)

Dit is handig als u met het hoofdformulier in een andere klasse dan het hoofdformulier werkt met een object dat in het hoofdformulier staat, maar geen InvokeRequired heeft

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

Het werkt hetzelfde als hierboven, maar het is een andere benadering als je geen object met invokerrequired hebt, maar wel toegang hebt tot het MainForm


Antwoord 15

In dezelfde lijn als eerdere antwoorden,
maar een zeer korte toevoeging die het mogelijk maakt om alle Control-eigenschappen te gebruiken zonder uitzondering voor het aanroepen van cross-threads.

Helper-methode

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

Voorbeeldgebruik

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

Antwoord 16

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

Antwoord 17

Bijvoorbeeld om de tekst uit een controle van de UI-thread te halen:

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

Antwoord 18

Dezelfde vraag: how-to-update- de-gui-van-een-andere-thread-in-c

Twee manieren:

  1. Retourwaarde in e.result en gebruik deze om uw tekstvakwaarde in backgroundWorker_RunWorkerCompleted-gebeurtenis in te stellen

  2. Declareer een variabele om dit soort waarden in een aparte klasse te houden (die zal werken als gegevenshouder) . Maak een statische instantie van deze klasse en je hebt er toegang toe via elke thread.

Voorbeeld:

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

Antwoord 19

Eenvoudige en herbruikbare manier om dit probleem te omzeilen.

Uitbreidingsmethode

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Voorbeeldgebruik

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

Antwoord 20

Gebruik gewoon dit:

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

Antwoord 21

Actie y; //aangegeven in de klas

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


Antwoord 22

Er zijn twee opties voor kruisdraadbewerkingen.

Control.InvokeRequired Property 

en de tweede is om

. te gebruiken

SynchronizationContext Post Method

Control.InvokeRequired is alleen nuttig bij het werken met besturingselementen die zijn overgenomen van de Control-klasse, terwijl SynchronizationContext overal kan worden gebruikt. Enkele nuttige informatie is de volgende links

UI voor het bijwerken van verschillende threads | .Net

UI voor het bijwerken van verschillende threads met behulp van SynchronizationContext | .Net

Other episodes