Hallo,
ich stelle gerade was ganz komisches fest: Ich habe eine BackgroundWorker, um Sachen in einem extra Thread zu machen. GUI updates geschehen in in der OnProgressChanged Funktion. Die sollte ja im Main Thread aufgerufen werden. Ich habe das auch schon oft so gemacht.
Nun bekomme ich aber die Fehlermeldung, dass das GUI Update aus dem falschen Thread heraus angetriggert wurde.
Wenn ich mir die ThreadId mit Environment.CurrentManagedThreadId anschaue, sehe ich auch, dass es drei Threads gibt:
Wie schafft man denn so etwas? Die BackgroundWorker Instanz wird auch im Main Thread erzeugt (habe ich kontrolliert). Die automatische Ausführung im GUI Thread ist der Hauptgrund, warum ich überhaupt einen BackgroundWorker benutze.
Im Moment steht deswegen meine Entwicklung und ich habe keine Ahnung, was ich da machen soll.
Wie sieht den der Code aus?
Ohne diesen können wir nur raten.
T-Virus
Developer, Developer, Developer, Developer....
99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.
Hier was von meinem Code:
public class DataTransactionWorker : BackgroundWorker
{
//**************************************************************************************************
public DataTransactionWorker()
{
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
RunWorkerAsync();
}
//***********************************************************************************************************
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
base.OnProgressChanged(e);
if (actTransaction == null) ProblemTrace.WriteLine("DataTransactionWorker.OnProgressChanged with actTransaction == null");
else
{
DebugTrace.WriteLine("DataTransactionWorker.OnProgressChanged with " + actTransaction.TraceName + " transaction");
actTransaction.onReady(transactionError, actTransaction);
}
actTransaction = null; //Hauptsächlich, damit das Objekt vom Garbage Collector freigegeben wird
//Der Thread wartet im Semaphore, bis OnProgressChanged abgearbeitet wurde
//Von daher hier die Semaphore frei geben
reportSemaphore.Release();
}
//***********************************************************************************************************
protected override void OnDoWork(DoWorkEventArgs e)
{
while (CancellationPending == false)
{
actTransaction = pendingTransactions[0];
DebugTrace.WriteLine("DataTransactionWorker takes transaction " + actTransaction.TraceName);
try
{
if (actTransaction.Send() == false) transactionError = DataTransaction.eError.SendFailed;
else actTransaction.Poll()
}
catch (Exception ex)
{
ProblemTrace.WriteLine("Exception " + ex.Message + " on poll of " + actTransaction.TraceName);
transactionError = DataTransaction.eError.Exception;
}
ReportProgress(1);
reportSemaphore.Wait();
}
}
public partial class MainForm : Form
{
readonly DataTransactionWorker dataTransactionWorker = new();
}
Die Trace, die ich benutze, geben auch die Threadid aus. Also sehe ich, in welchem Thread was läuft
In einer einfachen Test-Applikation funktioniert es. Da ist der Report immer in ThreadID 1
namespace WinFormsApp1
{
public partial class Form1 : Form
{
TPcTraceSet trace = PcTraceSetAccessor.Neu("Test", true);
BackgroundWorker worker = new();
public Form1()
{
InitializeComponent();
worker.WorkerReportsProgress = true;
worker.DoWork += Worker_DoWork;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void Worker_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
trace.WriteLine("Worker_ProgressChanged");
}
private void Worker_DoWork(object? sender, DoWorkEventArgs e)
{
trace.WriteLine("In Worker Task");
while (true)
{
Thread.Sleep(1000);
worker.ReportProgress(1);
}
}
}
}
Ein einfacher Test Task in meinem Programm
public class TestBackground : BackgroundWorker
{
public readonly TPcTraceSet trace;
//**************************************************************************************************
public TestBackground()
{
trace = PcTraceSetAccessor.Neu("TestBackground", true);
trace.MitThreadID = true;
trace.WriteLine("TestBackground Konstruktor");
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
RunWorkerAsync();
}
//***********************************************************************************************************
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
base.OnProgressChanged(e);
trace.WriteLine("TestBackground.OnProgressChanged");
}
//***********************************************************************************************************
protected override void OnDoWork(DoWorkEventArgs e)
{
trace.WriteLine("In Worker Task");
while (true)
{
Thread.Sleep(1000);
trace.WriteLine("Vor ReportProgress");
ReportProgress(1);
}
}
}
}
funktioniert aber nicht. Da sehe ich sogar, dass OnProgress während des Laufens auf einmal in einer anderen ThreadId ausgeführt wird.
Mein Programm ist etwas komplexer. Keine Ahnung, wie ich das wohl geschaft habe, dass das Framework nicht mehr richtig funktioniert. Nun muss ich mir vielleicht doch was anderes überlegen. Habe es vor Jahren schon mal mit eigenen Nachrichten an mein Fenster gemacht. Das war aber noch unter .Net Framework und nicht unter .Net Core.
Oder ich muss doch für jede Ausführung einen eigenen Thread erzeugen. Vielleicht ist das Thread Erzeugen ja doch nicht so teuer.
Oder als async machen. Da bin ich aber noch nicht so ganz sattelfest. Wäre aber auch ein Task/Thread.
Du arbeitest mit Tasks und hier gibt es keine Garantie, dass Dein Task immer im gleichen Thread bleibt. Der Task Scheduler übernimmt den Start, und auch das Management. Der Task kann jederzeit in einen anderen Thread verschoben werden.
Du musst selbst immer, absolut immer sicher stellen, dass Du Dich im Main Thread befindest, wenn Du auf die UI zugreifst.
Es gibt in modernem C# Code eigentlich keinen Grund mehr für die Nutzung des BackgroundWorkers.
async/await und Co sind Konzepte, die den BackgroundWorker vollständig ersetzt haben. Er existiert nur noch aus Kompatibilitätsgründen - und das schon länger als geplant, denn er wurde mal mit .NET 4.5 als Obsolete markiert.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich bin ja wohl willens, async/await zu benutzen. Mir fehlt da aber wohl noch etwas an Verständnis.
Bei HTTP Anwendungen habe ich es auch schon benutzt und als sehr angenehm erfahren. Aber da habe ich ja schon immer eine asynchrone Funktion, die ich einfach nur mit await aufrufen muss.
Jetzt habe ich aber eine fertige Methode.
Wie würde denn ein "normaler Poller" (das brauche ich ja erst einmal) mit async/await aussehen? Entweder denke ich zu kompliziert, oder... Ich habe auch noch kein vernünftiges Beispiel für so etwas gefunden. Praktisch brauche ich ja so etwas:
while (maximaleZeitNochNichtUm)
{
int result = myPoll(); //Hier wird ganz normal mit synchronem Code etwas abgefragt
if (result == 0)
{
//Ich bin fertig
return result;
}
Sleep(20)
}
//Timeout
return -2;
Guck dir Task.Delay mal an, das nutzt Du anstelle dem Sleep().
Und ggf. kannst Du auch das Polling selber asynchron aufbauen, die meisten Microsoft-APIs, bei denen es Sinn ergibt, unterstützen auch asynchrone Methoden.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Zitat von AmpelB
Wie würde denn ein "normaler Poller" (das brauche ich ja erst einmal) mit async/await aussehen?
Erste Frage: wieso ein Poller? Technisches Requirement? Normalerweise versucht man Poller zu vermeiden - kostet unnötig Ressourcen.
Wenn Du wirklich einen Poller brauchst, dann gibst dafür den PeriodicTimer.
Am Blogpost Run and manage periodic background tasks in ASP.NET Core 6 with C# hab ich mitgeschrieben und zeigt den Einsatz in ASP.NET Core - kann aber überall identisch verwendet werden. Das Forum führt ebenfalls gewisse Aufgaben Nachts via PeriodicTimer aus.
PS: Asynchrone Programmierung mit C# von Thomas Claudius Huber ist auch 9 Jahre später gerade für Einsteiger immernoch eine gute Quelle, um async/await besser zu verstehen.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
So etwas funktioniert und ist doch auch gar nicht so schlecht, oder?
private void button1_Click(object sender, EventArgs e)
{
trace.WriteLine("Start button1_Click");
asyncFunktion("Teil1: ");
trace.WriteLine("Ende button1_Click");
}
private async void asyncFunktion(string text)
{
trace.WriteLine("start von taskFunktion mit " + text);
int res = await Task.Run(() => pollerFunction(text));
trace.WriteLine("ende von taskFunktion mit " + text + ": res="+res);
}
private int pollerFunction(string text)
{
trace.WriteLine("start von pollerFunction mit " + text);
int iPoll = 0;
while (true)
{
iPoll++;
trace.WriteLine(iPoll + ". Poll von " + text);
if (iPoll >= 10) return 1;
Thread.Sleep(100);
}
}
Ein Paradebeispiel, was man eigentlich genau nicht tun soll.
Thread.Sleep wurde Dir zB schon mal erklärt, wieso das keine gute Idee ist. Steht weiter oben.
Absoluter Pitfall bei async/await.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Wenn ich das richtig sehe, benutzen sowohl der PeriodicTimer als auch Task.Delay einen eigenen Task für die Ausführung. Ich habe die gesamte Poller-Funktion ein einem Task. Für alle Poll-Vorgänge (bei 5mSek Interval und 200mSek Timeout sind das schon 80 polls) benötige ich somit nur einen Thread/Task. Der Main Thread wird auch nicht ausgebremst, da ja alles in einem extra Thread/Task ist. Und beim Pollen benötige ich die GUI ja nicht.
Also für mich spricht nichts gegen meine Lösung abe einiges gegen PeriodicTimer und Task.Delay.
Ich nehme mal an, dass Du zu diesem Fazit kommst, weil Du halt nicht weißt was Tasks sind und wie diese im Zusammenspiel mit Threads wirken.
Anders kann ich das Fazit "ein Task sei besser als mehrere" nicht interpretieren. Deine Lösung hat einfach eklatante Fehler, wie Thread.Sleep. Das untergräbt das gesamte Task-Konzept.
Klar, am Ende entscheidest Du, wie Du es umsetzen willst. Wenn das am Ende die Kopf-durch-die-Wand-Variante ist, dann ist das eben so.
Du kannst Dich auch einfach mit der Materie mal 2-3h beschäftigen, statt rum zu probieren. async/await und Tasks sind leider Konzeptthemen; daher nicht durch Try-and-Error verstehbar.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Hi,
schau dir deinen Code nochmal genau an und dann versuche zu verstehen, was Abt meint.
Du vermischt hier Äpfel mit Birnen und das ist bekanntlich nicht gut.
P.S.: PeriodicTimer sind aus meiner Sicht Goldwert wenn sie korrekt genutzt werden.
Hier, nen Bild das fast 10 Jahre alt ist - zeigt sehr vereinfacht , wie Tasks funktionieren. Hab nur noch das Bild mit Wasserzeichen, leider nicht mehr ohne.
https://cdn.mycsharp.de/forumpostattachments/attachment-13292.png
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code