Benutzer mit den meisten Antworten
Ungültiger threadübergreifender Vorgang /Backgroundworker/MSSQL

Frage
-
Hallo Community,
in meinem aktuellen Windows-Forms Programm baue ich, über einen ConnectionString, eine Verbindung zu einer MSSQL-Datenbank auf. Ist die Verbindung erfolgreich, lade ich eine Übersicht der Datenbanken in eine Combobox. Über einen Button starte ich einen Query, welcher mir das Ergebnis in einer Gridview ausgibt. Hier kommt es zu dem Problem, dass das Programm solange einfriert, bis die Abfrage abgeschlossen ist und ich die Werte in der Gridview angezeigt bekomme.
Nun habe ich ein wenig recherchiert und bin auf den Backgroundworker aufmerksam geworden. In meinem Event "backgroundworker1_DoWork" möchte ich nun den SQL-Query ausführen. Hier bekomme ich jedoch folgende Fehlermeldung:
System.InvalidOperationException: "Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement cmbDataBase erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde."
Hier mein Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using MaterialSkin.Controls;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Threading;
namespace SQLSearch
{
public partial class Form1 : Form
{
private BackgroundWorker myWorker = new BackgroundWorker();
private SqlConnection conn;
private SqlCommand command;
private SqlDataReader reader;
string sql = "";
string ConnectionString = "";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
groupBox2.Enabled = false;
}
private void btnConnect_Click(object sender, EventArgs e)
{
try
{
ConnectionString = "Data Source = " + txtServer.Text + "; User = " + txtUID.Text + "; Password = " + txtPWD.Text + "";
conn = new SqlConnection(ConnectionString);
conn.Open();
sql = "SELECT * FROM sys.databases d WHERE d.database_id>4";
command = new SqlCommand(sql, conn);
reader = command.ExecuteReader();
cmbDataBase.Items.Clear();
while (reader.Read())
{
cmbDataBase.Items.Add(reader[0].ToString());
MessageBox.Show("Verbindung erfolgreich hergestellt! Bitte wählen Sie nun eine Datenbank aus!");
groupBox2.Enabled = true;
txtServer.Enabled = false;
txtUID.Enabled = false;
txtPWD.Enabled = false;
cmbDataType.Enabled = false;
btnHelp.Enabled = false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnDisconnect_Click(object sender, EventArgs e)
{
groupBox2.Enabled = false;
txtServer.Enabled = true;
txtUID.Enabled = true;
txtPWD.Enabled = true;
}
private void btnstartsearch_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
conn = new SqlConnection(ConnectionString);
conn.Open();
sql = "SELECT cKey, cValue FROM " + cmbDataBase.Text + ".dbo.tOptions";
command.CommandTimeout = 600;
command = new SqlCommand(sql, conn);
command.ExecuteNonQuery();
try
{
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
DataTable dtsql = new DataTable();
adapter.Fill(dtsql);
BindingSource bSource = new BindingSource();
bSource.DataSource = dtsql;
dataGridView1.DataSource = bSource;
adapter.Update(dtsql);
dataGridView1.Columns[0].Width = 330;
dataGridView1.Columns[1].Width = 385;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}In einigen Foren wird bei einer solchen Fehlermeldung von einem Invoke gesprochen. Hat jemand Erfahrung damit und kann mir dabei helfen?
Vielen Dank im Voraus!
Antworten
-
Hallo Peter,
vielen Dank für die schnelle Rückmeldung. Ich habe mich ein wenig mit den Invokes auseinander gesetzt, bin jedoch absoluter Anfänger was das angeht. Könntest du mir anhand meines Codes ein Beispiel für Methode 1 nennen?
Vielen DankRobert
- Als Antwort markiert reapzz Freitag, 5. Oktober 2018 12:55
Alle Antworten
-
Hi,
die DoWork-Methode des BackGroundWorkers läuft in einem eigenen Thread. In dieser Methode darfst Du nicht auf Objekte zugreifen, die in einem anderen Thread erzeugt wurden und nicht threadsicher sind. Das ist in Deinem Fall die Instanz eines DataGridView, welche Du mit dataGridView1 ansprichst.Es gibt verschiedene Wege, das Problem zu lösen.
1. Möglichkeit: im RunWorkerCompleted-Ereignis greifst Du mit dataGrindView1 zu. Das funktioniert, da die Ereignisroutine im Ausgangs-Thread ausgeführt wird und es damit im Normalfall keinen thread-übergreifenden Zugriff gibt.
2. Möglichkeit: Du nutzt im anderen Thread (z.B. im DoWork) dataGridView1.Invoke. Damit werden die dort über einen Delegaten hinterlegten Befehle im Thread ausgeführt, in welchem das DataGridView instanziiert wurde (UI-Thread). Diese Befehlsfolge (im Invoke) wird dann in den Ui-Thread eingeordnet und ausgeführt, wenn dieser "Zeit hat".
3. Möglichkeit: Du nutzt den Synchronisation-Content mit Post, indem Du Die am Anfang den Context des UI-Thread merkst und ähnlich wie beim Invoke mit Post ein Programmstück im UI-Thread einordnest.
4. Möglichkeit: Du nutzt async und await anstelle des BackGroundWorkers, um die Programmierung und Übersichtlichkeit zu vereinfachen. Aber auch da musst Du thread-übergreifende Zugriffe unterbinden.
--
Viele Grüsse
Peter Fleischer (ehem. MVP für Developer Technologies)
Meine Homepage mit Tipps und Tricks
- Bearbeitet Peter Fleischer Freitag, 5. Oktober 2018 08:34
-
Hallo Peter,
vielen Dank für die schnelle Rückmeldung. Ich habe mich ein wenig mit den Invokes auseinander gesetzt, bin jedoch absoluter Anfänger was das angeht. Könntest du mir anhand meines Codes ein Beispiel für Methode 1 nennen?
Vielen DankRobert
- Als Antwort markiert reapzz Freitag, 5. Oktober 2018 12:55
-
Hi Robert,
nachfolgend mal aus Deinem Code, die das Laden mit dem BackGroudWorker demonstriert. Die Demo kannst Du direkt in ein leeres Projekt kopieren und dann nur die Ereignisroutine für das Form_Load mit dem Designer hinzufügen. Die restlichen Details sind alle im Demo-Code vorhanden. Der ConnectionString wird in der Demo aus den Einstellungen (Properties) geholt.using System; using System.ComponentModel; using System.Data; using System.Data.SqlClient; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { this.Controls.AddRange(new Control[] { dataGridView1, btnstartsearch }); myWorker.DoWork += MyWorker_DoWork; myWorker.RunWorkerCompleted += MyWorker_RunWorkerCompleted; btnstartsearch.Click += btnstartsearch_Click; } private Button btnstartsearch = new Button() { Text = "Start Suche", Dock = DockStyle.Top }; private DataGridView dataGridView1 = new DataGridView() { Dock = DockStyle.Fill }; private BackgroundWorker myWorker = new BackgroundWorker(); private BindingSource bSource; private DataTable dtsql; private void btnstartsearch_Click(object sender, EventArgs e) { myWorker.RunWorkerAsync(); } private void MyWorker_DoWork(object sender, DoWorkEventArgs e) { try { bSource = new BindingSource(); dtsql = new DataTable(); using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.cn)) { string sql = "SELECT * FROM Tab1"; using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn)) adapter.Fill(dtsql); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void MyWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { bSource.DataSource = dtsql.DefaultView; dataGridView1.DataSource = bSource; dataGridView1.Columns[0].Width = 330; dataGridView1.Columns[1].Width = 385; } } }
--
Viele Grüsse
Peter Fleischer (ehem. MVP für Developer Technologies)
Meine Homepage mit Tipps und Tricks- Bearbeitet Peter Fleischer Freitag, 5. Oktober 2018 11:47
-
Hi Peter,
ich versuche nun seit mehreren Stunden den Code umzuschreiben, sodass ich meinen ConnectionString nutzen kann, welchen ich aus den 3 Textboxen beziehe. Mit einem Klick auf den SearchButton bekomme ich jedoch immer wieder die Meldung, dass der Worker bereits arbeitet und, dass cmbDataBase bereits von einem anderen Thread genutzt wird.
-
Hi Robert,
ich weiß nicht, was du umgeschrieben hast, deshalb kann ich auch nicht erkennen, was da bei Dir falsch läuft. Ich kann auch nicht erkennen, wie cmbDataBase bei Dir aktualisiert wird und wie es genutzt wird. Läuft denn meine Demo bei Dir, wenn Du bis zur Anzeige wartest?Wenn Du natürlich den arbeitenden BackGroundWorker versuchst, nochmals zu "starten", dann geht das nicht. Wenn Du mit einem BackGroundWorker arbeiten willst, dann musst Du eine Warteschlange organisieren oder eine bereits laufende Abfrage abbrechen, um dann die nächste zu starten.
--
Viele Grüsse
Peter Fleischer (ehem. MVP für Developer Technologies)
Meine Homepage mit Tipps und Tricks