Hi,
ich habe folgende Situation:
In meiner Anwendung lasse ich einen MultimediaTimer laufen um sehr zeitgenau und regelmäßig eine Aktion durchführen zu können (der Standard-Timer war hierfür nicht genau genug). Um die Timerausführung auch bei weiteren Aktionen in der GUI auf keinen Fall zu stören, lagere ich die Erzeugung und das Tick-Event über einen Backgroundworker in einen zweiten Thread aus. Das funktioniert soweit auch so wie gedacht.
Und jetzt zum Problem bei der Sache:
Ich muss die Ausführung des TickEvents in der GUI anzeigen. Gemäß [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) habe ich mir also gedacht, dass das ProgressChanged-Event des BackgroundWorkers genau das ist was ich suche. Leider klappt das nicht. Da ich im Worker die Klasse nur erzeuge, in der dann der MMTimer gestartet wird, bekomme ich beim Aufruf von ProgressChanged im Tick-Event dann die Meldung "Für diesen Vorgang wurde OperationCompleted bereits aufgerufen. Weitere Aufrufe sind unzulässig."
Wie komme ich aus dieser Misere?
==============================
Wenn ichs wüsst', würd' ich nicht fragen!!! 😁
==============================
Wie komme ich aus dieser Misere?
Indem du den Backgroundworker weglässt oder im BackgroundWorker DoWork event so lange was machst, bis du den Timer nicht mehr brauchst. Aber der BackgroundWorker ist dafür eigentlich nicht gedacht.
Darth Maim
Du meinst ich sollte lieber direkt mit "Thread" arbeiten?
==============================
Wenn ichs wüsst', würd' ich nicht fragen!!! 😁
==============================
Das funktioniert soweit auch so wie gedacht.
Bisschen Code zum Verständnis wäre nicht schlecht. Was erzeugst Du wo und wie? Was hat der BackgroundWorker mit dem Multimedia-Timer zu tun?
Bisschen Code zum Verständnis wäre nicht schlecht.
Ok, hier mal zum Verständnis:
class Main
{
BackgroundWorker worker = new BackgroundWorker();
Working_Hard wh_ref;
worker_DoWork()
{
Working_Hard wh = new Working_Hard()
wh_ref = wh;
wh.starte_timer();
}
button1_click()
{
worker.RunWorkerAsync();
}
button2_click()
{
wh_ref.verändere_parameter(parameter);
}
}
class Working_Hard
{
MMTimer timer = new MMTimer();
starte_timer()
{
timer.Start();
}
timer_Tick()
{
//mache was zeitkritisches ...
}
verändere_parameter(parameter)
{
//Parameter ändern, die im Tick-Event verarbeitet werden ...
}
}
Das ist es in groben Zügen. Im Hauptprogramm starte ich den Vorgang per button1, der dann im Hintergrund den Timer startet. Der wiederum wird im Hintergrund regelmäßig durchlaufen über das Tick-Event im zweiten Thread. Ich habe auch die Möglichkeit über meine Referenzklasse wh_ref dem Vorgang auch im Nachhinein noch andere Parameter, die z.B. durch den Benutzer eingegeben wurden, zu verändern.
==============================
Wenn ichs wüsst', würd' ich nicht fragen!!! 😁
==============================
Hallo,
das Problem ist dass deine DoWork Methode lange schon fertig ist bis du das Timer-Tick kriegst und damit dann den Progress festlegen willst
D.h. der BackgroundWorker hat seine Arbeit schon erledigt (DoWork ist fertig) und du willst später erst sagen. So jetzt habe ich 10% gemacht, was nicht sein kann da er schon fertig hat.
Irgendwie ist der gesamte Code irgendwie sehr schwierig überschaubar. Sind zwar nur ein paar Zeilen aber wirklich strukturiert wirkt das nicht.
Statt den BackgroundWorker kannst du einfach einen Thread verwenden. In deiner Working_Hard Klasse kannst du ein Event einbauen welches du schmeisst wenn sich der Fortschritt ändert. Dieses Event abonnierst du im MainWindow und gibst dann den Wert auf der GUI aus.
Du darfst dabei aber nicht das Invoken vergessen da du dich in einen anderen Thread befindest.
Grüße
Michael
das Problem ist dass deine DoWork Methode lange schon fertig ist bis du das Timer-Tick kriegst und damit dann den Progress festlegen willst
Ja genau. Der BW dient hier quasi nur zum Einstieg in den zweiten Thread. Die Thread-Variante macht wohl wirklich deutlich mehr Sinn. Ich werde mir das mal entsprechend umbauen und mit eigenem Event umsetzen.
Danke!
==============================
Wenn ichs wüsst', würd' ich nicht fragen!!! 😁
==============================
Das Problem ist, dass nachdem du den Timer gestartet hast (also nach wh.starte_timer()), die DoWork Methode vorbei ist und damit der BackgroundWorker nicht mehr läuft. Dann kannst du natürlich nicht mehr die ProgressChanged Methode aufrufen.
Aber da meiner Meinung nach das tick event sowieso nicht im GUI, bzw. dem Thread, der den Timer gestartet hast, brauchst du das doch alles garnicht mit dem BackgroundWorker.
Also im Grunde sowas:
class Program {
private WorkingHard _wh;
private void startWork() {
_wh = new WorkingHard();
_wh.Start();
_wh.StatusUpdate += (o,e) => this.Invoke(new Action<object, WorkingHardEventArgs>(wh_StatusUpdate), o, e);
}
private void updateParameters(Foo bar) {
_wh.updateParameter(bar);
}
private void wh_StatusUpdate(object sender, WorkingHardEventArgs e) {
this.progressBar.Value = e.Percentage;
}
}
class WorkingHard {
MMTimer _timer = new MMTimer();
public void Start() {
_timer.Start();
}
private void Tick() {
//Do something
OnStatusUpdate(42);
}
private void OnStatusUpdate(int percentage) {
if(StatusUpdate != null) StatusUpdate(percentage);
}
public event EventHandler<WorkingHardEventArgs> StatusUpdate;
}
class WorkingHardEventArgs : EventArgs {
public WorkingHardEventArgs(int percentage) {
_percentage = percentage;
}
private int _percentage = 0;
public int Percentage { get { return _percentage; } }
}
Falls das Tick-Event des MMTimers doch im gleichen Thread ausgeführt wird, in dem er erstellt wurde, mach die Start-Methode einfach so:
ThreadPool.QueueUserWorkItem(o => _timer.Start());
Darth Maim
Aber da meiner Meinung nach das tick event sowieso nicht im GUI, bzw. dem Thread, der den Timer gestartet hast, brauchst du das doch alles garnicht mit dem BackgroundWorker.
Das ist richtig. Es gibt ggf. ein kleines Aber. Wenn es sich bei der MMTimer-Klasse um die Klasse von Codeproject handelt und ich mich recht erinnere, so hatte diese ein SynchronizationObject (o. ä.). Das darf man natürlich nicht benutzen (s. Quelltext). Vielleicht willst Du es aber auch gerade verwenden, um direkt auf die GUI zugreifen zu könnnen?
Interessant wäre dann noch, wie lange Deine "zeitkritische" Operation gegenüber dem Intervall des Timers dauert?
@michlG:
Perfekt! So klappts. Ich habs jetzt per Thread umgesetzt und den Backgroundworker komplett rausgeschmissen. Das Problem der Aktualisierung löse ich über ein in die Worker-Klasse eingebautes Event, das ich im Hauptprogramm abonniere. Im Eventhandler Invoke ich die zu verwendeten Elemente des Main-Threads und schwups ... es klappt so wie ich das wollte! Danke noch mal!
@Darth Maim:
Stimmt, ich hatte es zu Anfang auch genau so (ohne zweiten Thread) genau so umgesetzt, da ich auch so dachte. Das Problem ist aber hierbei, dass das Tick-Event selbst zar in einem zweiten Thread läuft, aber der Aufruf des Tick-Events wird trotzdem ausgebremst, wenn das Hauptprogramm beschäftigt ist.
==============================
Wenn ichs wüsst', würd' ich nicht fragen!!! 😁
==============================