Ich habe eine Server/Client-Anwendung mit einem Pluginsystem auf der Clientseite. Diese werden über eine seperate Domain im Clienthauptprogramm geladen. In einem Modul ist eine längerdauernde Funktion, die Daten aus eine Datenbank in Excel umkopiert. Um die GUI nicht zu blockieren, wurde diese Funktion in einen Backgroundworker ausgelagert. Im Plugin wird ein Fortschrittssfenster geöffnet und der Backgroundworker gestartet. Dieser aktualisiert dann einen Fortschrittsbalken auf dem Formular. Dies funktioniert alles bestens. Nun ist mein Programm aber mehrsprachig aufgebaut (über die mechanismen von .NET, also Resourcen ...). Hier mal die Code-Relevanten Teile:
Plugin:
private void RunBackground()
{
frmFortschritt = new Fortschritt(this.Bounds);
frmFortschritt.Max = (akt + ms + 1) * frmWahl.Gewaehlt.Count;
frmFortschritt.Closed += new EventHandler(frmFortschritt_Closed);
frmFortschritt.Cancel = Export;
frmFortschritt.Show();
bgwExcel.DoWork += new DoWorkEventHandler(bgwExcel_DoWork);
bgwExcel.ProgressChanged += new ProgressChangedEventHandler(bgwExcel_ProgressExport);
bgwExcel.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwExcel_ExportCompleted);
bgwExcel.WorkerReportsProgress = true;
bgwExcel.WorkerSupportsCancellation = true;
bgwExcel.RunWorkerAsync(Export);
}
private void bgwExcel_ProgressExport(object sender, ProgressChangedEventArgs e)
{
Show_Fortschritt();
}
private void bgwExcel_ExportCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((bool)e.Result)
{
frmFortschritt.Set_Text(Properties.Resources.DatenWurdenExportiert);
//Show_Text(Properties.Resources.DatenWurdenExportiert);
}
}
delegate void SetTextCallback(string text);
private void Show_Text(string Text)
{
if (frmFortschritt != null)
{
if (frmFortschritt.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(Show_Text);
frmFortschritt.Invoke(d, new object[] { Text });
}
else
frmFortschritt.Set_Text(Text);
}
}
private void Show_Fortschritt()
{
if (frmFortschritt != null)
{
// if (frmFortschritt.InvokeRequired)
// {
// frmFortschritt.Invoke(new MethodInvoker(Show_Fortschritt));
// return;
// }
frmFortschritt.Aktuell = frmFortschritt.Aktuell + 1;
frmFortschritt.Set_Text();
}
}
Fortschrittssdialog:
public int Aktuell
{
get { return (pgbFortschritt.Value); }
set { pgbFortschritt.Value = value; } // <-- Hier Fehler
}
public void Set_Text(string Text)
{
label1.Text = Text; //<-- Hier Fehler
btnCancel.Text = "OK";
}
Wenn ich das Programm jetzt starte, dann bekomme ich beim Aktualisieren des Fortschrittbalkens (im ProgressChanged) und beim Completed die Fehlermeldung : "Ungültiger threadübergreifender Vorgang". Wenn ich dann den auskommentierten Teil einbaue, dann funktioniert das Programm wieder, allerdings sind dann die Texte in Deutsch (also Defaultsprache). Kann mir jemand helfen, wie ich das Problem löse, so dass die Texte in der gewünschten Sprache ohne CrossThreadError angezeigt werden?
das war Blödsinn von mir, Du hast ja ein Return drin, was das Else auch nicht viel besser macht ;-)
Eventuell liegt das Problem bei
frmFortschritt.Set_Text();
Ich finde nur komisch, dass Du nicht die Eigenschaften und Methoden innerhalb Deiner Form Threadsicher gestaltest, so dass der Workerthread nicht darauf achten muss, ob ein "Invoke required" ist.
Also ich würde an Deiner Stelle alle Eigenschaften und Methoden, die von Threads aufgerufen werden können (im Detail z.B. "frmFortschritt.Aktuell", "frmFortschritt.Set_Text();" ...) so erweitern, dass sie dann selbst nachfragen, ob ein "Invoke required" ist.
Dann wäre die Threadsicherheit gekapselt und betrifft ALLE Threads, die sich Deiner Form begnügen.
Grüße
Norman-Timo
A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”
das Problem wurde von mir vielleicht nicht ganz klar dargestellt. Bis zur Umstellung auf Mehrsprachigkeit (Localizable = false) hat ja alles mittels Backgroundworker funktioniert (der kümmert sich ja um das Invoke selbst). Nachdem ich aber Localizable auf true gesetzt habe musste ich das Invoke selbst noch abfragen (daher auch dies im Workerthread). Wenn ich das mache funktioniert ja alles wieder (keine Fehlermeldung) nur die Texte sind dann eben nicht in der gewünschten Sprache sondern in der Default. Für mich sieht es so aus, alsob der Workerthread eben in einem Defaultthread läuft und die Sprache noch in einem zusätzlichen Thread gestartet wird, aus dem dann der Backgroundworker arbeitet.
Danke für den Tip. Ich musste nur im Backgroundworker in der Dowork-Methode über Thread.CurrentUICulture die aktuelle Sprache setzen und schon geht alles wieder (auch ohne InvokeRequired)
Wie reichst du die Sprachänderung von der GUI an deinen Backgroundworker weiter?
Ich habe gerade ein ähnliches Problem mit mehreren Threads (und div. Timer, die Aktionen auslösen). Wenn ich in der GUI die Sprache ändere, so betrifft das nur den einen Thread. Mir fehlt eine Möglichkeit, für alle laufenden Threads die Sprache zu ändern.
Ja, das ist mir mittlerweile klar, aber wie realisiere ich es, dass ich alle Threads benachrichtige? Ich habe es bisher nur geschafft, mit Thread.CurrentThread... die Culture zu setzen, weiß aber nicht, wie ich die anderen Threads "benachrichtige".
Ja, das ist mir mittlerweile klar, aber wie realisiere ich es, dass ich alle Threads benachrichtige?
Threads benachrichtigen geht nicht, dass ist der falsche Weg. Deine Threads können maximal prüfen, ob sich der Status geändert hat und darauf entsprechende Anweisungen ausführen.
Es ist aber unsinnig, während der Ausführung einer bestimmten Aufgabe, diverse relevante Daten zu ändern. Das ändern der Kultur für alle Threads könnte ungeahnte Konsequenzen hervorrufen. Es ist sinniger, dass nur neugestartete Threads bzw. welche aus dem Threadpool (direkt als erste Anweisung) die entsprechende neue Kultur sich holen. Laufende Threads müssten weiterlaufen so wie sie sind oder abgebrochen und neugestartet werden.
Eine andere Überlegung wäre, dass die Threads kultur-neutral arbeiten und erst innerhab des Gui-Threads (Control.Invoke) die entsprechende Ressource mit der aktuellen Kultur geladen wird.
Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...
Mein Fehler ist vermutlich, dass ich meine Ressourcen zu früh hole, und zwar in den Hintergrundthreads. Vermutlich sollte ich darauf achten, sämtliche Ressourcen erst im Hauptthread (nach Invoke) abzurufen und von dort zu setzen.
Ich werde mal prüfen, ob das den gewünschten Effekt hervorruft.
Da es sich teilweise um Events von Timern oder Hardware-Ereignisse handelt, ist dies nicht möglich. Die Sprache meiner Oberfläche kann sich zudem jederzeit ändern.