InvalidOperationException - Ungültiger Threadübergreifender Zugriff trotz Vermeidung von Control.Attribut-Zugriff

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von Mono.

    InvalidOperationException - Ungültiger Threadübergreifender Zugriff trotz Vermeidung von Control.Attribut-Zugriff

    Servus miteinander!

    Als ich gestern in diesem Beitrag: kann CheckForIllegalCrossThreadCalls Probleme verursachen? hier großspurig vom Umstellen eines meiner Projekte, auf ein anderes (Code-)Design, bin ich grad der Ernüchterung erlegen: So wie ich das vorhatte funzt es einfach ned :(

    Im Prinzip handelt es sich dabei um 2 Klassen: Eine Form-Klasse, die über Fabrik-Klassen seine Controls erstellt und um eine reine Logik-Klasse, die Datenbabkanfragen macht, und dann die Daten in das UI schreibt. Dass das an sich ein Codetechnischer Mist ist, braucht mir hier bitte keiner erzählen - hab das a) in einer Woche geschrieben und b) war's egal wie, Hauptsache es lief).

    Grob der genauere Ablauf: Die Form-Klasse(Form1) hat im Konstruktor seine Controls auf sich erstellt und im Form-Load Event wurde in einer Endlosschleife und await ein Task gestartet, der eine statische Methode aus einer anderen Klasse aufgerufen hat, die andere Klasse hat darauf hin eine Datenbank abgefragt und das Resultat anschließend per Referenz auf die Form in die publiken Attributen(waren Dictionary aus String als Key (Name des Controls) und Control als Value) geschrieben.
    Was ich im letzen Satz erkläre, wurde per Control.Invoke(Action()) gemacht.

    Ich hab mich also mal hingesetzt und wollte das ganze besser lösen. In der Verbesserung sollte das ganze ermöglicht werden, indem ich in der Datenbankabfrage-Klasse(QueryDatabase), sobald ich mir den Abfragen durch bin, ein Event auslösen(QueryComplete), welches als EventArgs-Attribut(QueryDatabaseEventArgs) ein Dictionary<string, int> haben. Hierbei ist der string als Key der Name des Controls und int der Wert, der auf dem Controls als Text stehen soll (Controls sind eigentlich nur Labels).

    Doof nur, dass ich eine besagte InvalidOperationException bekomme, sobald ich den Text der Controls setzen will (oder vielmehr, sobald ich ihn mit einem Wert aus dem Data-Attribut meines EventArgs belege). Muss dazu sagen, ich hab das ganze sowohl Event-basiert, als auch mit Referenzen und Aufruf von publiken Methoden gelöst (bzw versucht) => beide male das selbe Ergebnis

    Was ich im Verdacht hatte, war, dass es evtl daran lag, dass ich mir die Werte von einem Event hole, das von einer Klasse ausgelöst wurde, von der eine Methode in einem anderen Thread aufgerufen wurde, deswegen habe ich mir dann beide Projekte (einmal Event-basiert und einmal als reiner Methodenaufruf) zusätzlich als "mit Men in the Middle", sprich einer Interface-Klasse/Stellvertreter-Klasse gebaut(QueryDatabaseInterface). Dabei hat die Form1-Klasse keinerlei Zugriff auf die QueryDatabase-Klasse (und anderes herum natürlich auch ned ;D).

    Form1 ruft im Load-Event auf QueryDatabaseInterface eine Run()-Methode auf. Diese ruft wiederum auf der QueryDatabase-Klasse, von der QueryDatabase eine Referenz hat, eine Methode auf, die das Query durchführt. Nach Beendigung des Query wird ein Event(QueryComplete) ausgelöst, für das es in QueryDatabaseInterface einen entsprechenden Handler gibt. In dem Handler wiederum wird über eine Referenz, die QueryDatabaseInterface auf Form1 hat, eine UpdateUI-Methode aufgerufen, welche als Parameter ein Dictionary<string, int> enthält, um damit dann die UI-Elemente zu betexten.

    Nur leider habe ich hier genau das selbe Problem mit dem angeblichen Threadübergreifenden Zugriff.
    Ich würde euch bitte, euch das ganze mal anzusehen und evlt kann mir ja wer dabei helfen ;D

    Lg Radinator

    PS: Ich hänge mal alle vier Projekte an.
    Dateien
    • UpdateUI.zip

      (250,88 kB, 120 mal heruntergeladen, zuletzt: )
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    @Radinator Fang die Exceptions ordentlich auf, dann kannst Du wenigstens ein paar Informationen rausholen:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Windows.Forms;
    3. namespace UpdateUIWithEvents
    4. {
    5. static class Program
    6. {
    7. /// <summary>
    8. /// Der Haupteinstiegspunkt für die Anwendung.
    9. /// </summary>
    10. [STAThread]
    11. static void Main()
    12. {
    13. Application.EnableVisualStyles();
    14. Application.SetCompatibleTextRenderingDefault(false);
    15. Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
    16. AppDomain currentDomain = AppDomain.CurrentDomain;
    17. currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);
    18. Application.Run(new Form1());
    19. }
    20. static void MyHandler(object sender, UnhandledExceptionEventArgs args)
    21. {
    22. Exception ex = (Exception)args.ExceptionObject;
    23. Console.WriteLine(ex.Message);
    24. Console.WriteLine(ex.StackTrace);
    25. }
    26. }
    27. }
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    iwie scheint mir die Erzählung ziemlich wirr, also in dem Sinne, dass da iwie zuviele dran beteiligt sind: Form, Fabrik, Logik, Datenbank, Threading, andere Klasse, ...

    Ich würde empfehlen, den Threading-Kram rauswerfen, und die Geschichte erstmal so einfach wie möglich, und synchron ans laufen bringen.

    Dann den Async-Pattern verbauen, damits dann auch asynchron läuft.
    @RodFromGermany:
    Weiterhin diese InvalidOperationException. Als Beschreibung des Fehlers nennt mir VS weiterhin den "illegalen threadübergreifenden Zugriff und markiert mir die Zeile, in der ich .Text = ... Schreibe

    @ErfinderDesRades:
    Hatte gehofft, dass ich mich deutlich genug ausgedrückt habe. Schade.
    Vereinfacht gesagt - falls ich das nicht so rüber gebracht habe - will ich das Aktualisieren der Anzeige nicht der QueryDatabase Klasse überlassen sondern die QueryDatabase Klasse soll dem UI sagen, wann es fertig ist u d veranlassen, dass das UI die Daten bekommt (per Event oder als Parameter) und im Anschluss das UI seine Controls selber neu betextet.

    Das mit dem Threading und den 5Sekunden warten hab ich nur gemacht, dass nicht jede Sekunde ein neuer Datenbankabfrage gestartet und mir das Netz nicht überlastet wird. Die 2 Sekunden sind nur als optischer Stopper gedacht zwecks Sehen, dass sich die Daten ändern
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Ok das das an dem Threading liegt, verstehe ich. Nur wie kann ich dann sicherstellen, dass ich nicht jede Sekunde ne Abfrage losschicke? Und mir damit das Netzwek zumülle?
    Wenn ich nämlich nur das Warten per Thread.Sleep(5000) löse, dann friert mir ja das UI solange ein. Ebenso das Mit der Abfrage (dauert ~0,5 Sekunden).
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Radinator“ ()

    Radinator schrieb:

    per Thread.Sleep(5000)
    Per Timer friert da nix ein.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany: Ja hast Recht, da friert nix ein.
    Weiß ehrlich gesagt selber ned, warum ich bisher ned an Timer gedacht habe :rolleyes:

    Ich denk ich werd es mal mit dem System.Threasding.Timer versuchen.

    Lg Radinator
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell

    Radinator schrieb:

    Timer
    Ein WinForms-Timer läuft im GUI-Thread, ein Threading.Timer läuft in einem anderen Thread. Teste ggf. beide Varianten.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @ErfinderDesRades: Jo...leider :( Genau so auch der System.Timers.Timer Lediglich der System.Windows.Forms.Timer macht keine Probleme

    UpdateUI

    Event-Basiert

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Drawing;
    4. using System.Windows.Forms;
    5. namespace UpdateUIWithEvents
    6. {
    7. public partial class Form1 : Form
    8. {
    9. private QueryDatabase queryDatabase;
    10. private List<Button> buttons;
    11. private System.Windows.Forms.Timer timer;
    12. public Form1()
    13. {
    14. InitializeComponent();
    15. this.buttons = new List<Button>();
    16. this.queryDatabase = new QueryDatabase();
    17. this.queryDatabase.QueryComplete += this.OnQueryComplete;
    18. this.timer = new Timer()
    19. {
    20. Interval = 5000
    21. };
    22. this.timer.Tick += (s, e) =>
    23. {
    24. this.queryDatabase.query();
    25. };
    26. this.FormClosing += (s, e) =>
    27. {
    28. this.timer.Stop();
    29. };
    30. this.Load += (s, e) =>
    31. {
    32. buildUI();
    33. this.timer.Start();
    34. };
    35. }
    36. private void buildUI()
    37. {
    38. Button btn;
    39. for (int i = 0; i < 20; i++)
    40. {
    41. btn = new Button()
    42. {
    43. Name = "btn" + i.ToString(),
    44. Text = "btn" + i.ToString(),
    45. Location = new Point(0, i * 20 + 5),
    46. Width = 50,
    47. Height = 20
    48. };
    49. this.buttons.Add(btn);
    50. this.Controls.Add(btn);
    51. }
    52. }
    53. private void OnQueryComplete(object sender, QueryCompleteEventArgs e)
    54. {
    55. Dictionary<string, int> dic = e.Data;
    56. foreach (KeyValuePair<string, int> element in dic)
    57. {
    58. this.buttons.Find((x) => x.Name == element.Key).Text = element.Value.ToString();
    59. }
    60. }
    61. }
    62. public class QueryDatabase
    63. {
    64. public event EventHandler<QueryCompleteEventArgs> QueryComplete;
    65. public void query()
    66. {
    67. Dictionary<string, int> dic = new Dictionary<string, int>();
    68. Random rnd = new Random();
    69. for (int i = 0; i < 20; i++)
    70. {
    71. dic.Add("btn" + i, i * rnd.Next(10, 100));
    72. }
    73. QueryComplete(this, new QueryCompleteEventArgs() { Data = dic });
    74. }
    75. }
    76. public class QueryCompleteEventArgs : EventArgs
    77. {
    78. public Dictionary<string, int> Data { get; set; }
    79. public QueryCompleteEventArgs()
    80. {
    81. this.Data = new Dictionary<string, int>();
    82. }
    83. }
    84. }


    Aufruf-Basiert

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Drawing;
    4. using System.Threading.Tasks;
    5. using System.Windows.Forms;
    6. namespace UpdateUIWithCall
    7. {
    8. public partial class Form1 : Form, IBeobachter
    9. {
    10. private QueryDatabase queryDatabase;
    11. private List<Button> buttons;
    12. private Timer timer;
    13. public Form1()
    14. {
    15. InitializeComponent();
    16. this.buttons = new List<Button>();
    17. this.queryDatabase = new QueryDatabase();
    18. this.queryDatabase.registriereBeobachter(this);
    19. this.timer = new Timer()
    20. {
    21. Interval = 5000
    22. };
    23. this.timer.Tick += (s, e) =>
    24. {
    25. this.queryDatabase.query();
    26. };
    27. this.FormClosing += (s, e) =>
    28. {
    29. this.timer.Stop();
    30. };
    31. this.Load += (s, e) =>
    32. {
    33. buildUI();
    34. this.timer.Start();
    35. };
    36. }
    37. private void buildUI()
    38. {
    39. Button btn;
    40. for (int i = 0; i < 20; i++)
    41. {
    42. btn = new Button()
    43. {
    44. Name = "btn" + i.ToString(),
    45. Location = new Point(0, i * 20 + 5),
    46. Width = 50,
    47. Height = 20
    48. };
    49. this.buttons.Add(btn);
    50. this.Controls.Add(btn);
    51. }
    52. }
    53. void IBeobachter.aktualisieren()
    54. {
    55. foreach (KeyValuePair<string, int> element in this.queryDatabase.Data)
    56. {
    57. this.buttons.Find((x) => x.Name == element.Key).Text = element.Value.ToString();
    58. }
    59. }
    60. }
    61. public class QueryDatabase : ISubjekt
    62. {
    63. private List<IBeobachter> beobachterListe;
    64. public Dictionary<string, int> Data { get; private set; }
    65. public QueryDatabase() {
    66. this.beobachterListe = new List<IBeobachter>();
    67. this.Data = new Dictionary<string, int>();
    68. }
    69. public void query()
    70. {
    71. Task.Delay(2000);
    72. Dictionary<string, int> dic = new Dictionary<string, int>();
    73. Random rnd = new Random();
    74. for (int i = 0; i < 20; i++)
    75. {
    76. dic.Add("btn" + i, i * rnd.Next(10, 100));
    77. }
    78. this.Data = dic;
    79. this.aktualisiereBeobachter();
    80. }
    81. public void aktualisiereBeobachter()
    82. {
    83. foreach (IBeobachter beobachter in this.beobachterListe)
    84. {
    85. beobachter.aktualisieren();
    86. }
    87. }
    88. public void entferneBeobachter(IBeobachter beobachter)
    89. {
    90. this.beobachterListe.Remove(beobachter);
    91. }
    92. public void registriereBeobachter(IBeobachter beobachter)
    93. {
    94. this.beobachterListe.Add(beobachter);
    95. }
    96. }
    97. public interface ISubjekt
    98. {
    99. void registriereBeobachter(IBeobachter beobachter);
    100. void entferneBeobachter(IBeobachter beobachter);
    101. void aktualisiereBeobachter();
    102. }
    103. public interface IBeobachter
    104. {
    105. void aktualisieren();
    106. }
    107. }


    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Du schreibst doch was in die GUI aus dem Timer Thread:

    VB.NET-Quellcode

    1. this.buttons.Find((x) => x.Name == element.Key).Text = element.Value.ToString();
    Das ist meine Signatur und sie wird wunderbar sein!
    @Mono: Beziehst du dich jetz auf den Code aus meinem letzen Post in den Spoilern oder auf den Code aus einem der vier Projekte aus dem Startpost?

    Im ersten Fall: Naja ich löse halt im GUI-Thread (Tick-Event) ein QueryComplete-Event aus, welches in der GUI-Klasse einen entsprechenden Handler findet, in dem hold ich mir aus e.Data das Dictionary und betexte die Daten neu.
    Im zweiten Fall: Im prinzip das selbe, nur dass ich das Auslösen des QueryComplete-Events im Nicht-GUI-Thread/Task auslöse.

    Mir will nur nicht einleuchten, warum das ganze ned funzt, wenn ich im Nicht-GUI-Thread das Event QueryComplete feuere und sich das UI bzw die UI-Klasse im Eventhandler die Daten holt und seine Controls neu betextet.
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell

    Radinator schrieb:

    Mir will nur nicht einleuchten, warum das ganze ned funzt, wenn ich im Nicht-GUI-Thread das Event QueryComplete feuere und sich das UI bzw die UI-Klasse im Eventhandler die Daten holt und seine Controls neu betextet.

    Weil wenn das Event im NebenThread gefeuert wird, dann wird es auch im NebenThread empfangen.
    Und wenn das UI dann seine Controls zu betexten sucht isses im falschen Thread.