Laden...

Threading - Wie denn nun?

Erstellt von inTrance vor 18 Jahren Letzter Beitrag vor 18 Jahren 2.741 Views
inTrance Themenstarter:in
170 Beiträge seit 2005
vor 18 Jahren
Threading - Wie denn nun?

Hallo!

Denn ganzen Nachmittag bin ich nun am Rätseln, wie man Arbeiten im Hintergrund
am besten löst. Es gibt in .NET anscheinend ja x Möglichkeiten Threads anzuwerfen
(BackgroundWorker, class Thread, asynchrone Delegates, ...).

Ich steig durch diese ganzen Möglichkeiten einfach nicht durch. Vielleicht kann mir
einer ja Tipps geben, welche Methode für meine Ansprüche die passende ist:

  • Die "Working-Methode" soll nichts über die GUI wissen
  • Von außen soll der (prozentuale) Status abgefragt werden können
  • Der Caller soll über die Fertigstellung via Callback informiert werden und
    dadurch das Ergebnis erhalten

Eine Konzept-Frage habe ich auch noch 😉 : Ich habe eine Klasse, die mehrere
Methoden hat, die teils sehr lange in der Ausführung brauchen. Sollte die Klasse
jetzt selbst den Thread starten und den Caller über die Fertigstellung informieren?
Oder sollte der Caller einen Thread starten, der die Methode aufruft??

// Edit: Zweiter Fall macht ja keinen Sinn, da man dann keinerlei Infos über den
Status erhalten kann...

Grüße und vielen Dank für Tipps,

inTrance

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo inTrance,

  • Die "Working-Methode" soll nichts über die GUI wissen
  • Von außen soll der (prozentuale) Status abgefragt werden können
  • Der Caller soll über die Fertigstellung via Callback informiert werden und
    dadurch das Ergebnis erhalten

Du kannst ganz normale Threads dafür nehmen, musst aber wissen, dass die Events dann im diesem Thread ausgeführt werden. Wenn du in den EventHandlern auf das GUI zugreifen willst, musst du dann also dort BeginInvoke verwenden. Der BackgroundWorker hat etwas mehr Overhead beim Erzeugen, dafür laufen die EventHandler automatisch im GUI-Thread ab.

Ich habe eine Klasse, die mehrere Methoden hat, die teils sehr lange in der Ausführung brauchen. Sollte die Klasse jetzt selbst den Thread starten und den Caller über die Fertigstellung informieren? Oder sollte der Caller einen Thread starten, der die Methode aufruft??

Dafür gibt es "Multithreaded Programming with the Event-based Asynchronous Pattern". Die Methoden der Klasse würde also intern asynchron arbeiten und der Aufrufer registriert einen EventHandler, um mitzukriegen, ob die Methode fertig ist. Die Klasse könnte auch einen Event für Fortschrittsmeldungen definieren. Das würde also auch deine Forderungen erfüllen. Allerdings habe ich heute so ziemlich den ganzen Tag gebraucht, um diesen Pattern zu verstehen. Und ich denke, ich kenn mich einigermaßen aus. Einsteiger wären hier auf jeden Fall überfordert.

herbivore

4.221 Beiträge seit 2005
vor 18 Jahren

Schau Dir unbedingt auch noch dies an:

Komponenten mit mehreren Threads

Es handelt sich hier um einen Prototype um Komponenten zu erstellen welche in einem anderen Thread laufen, die Events aber intern in den UI-Thread marshallen.

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

inTrance Themenstarter:in
170 Beiträge seit 2005
vor 18 Jahren

Danke für deine Antwort!

Du kannst ganz normale Threads dafür nehmen, musst aber wissen, dass die Events dann im diesem Thread ausgeführt werden. Wenn du in den EventHandlern auf das GUI zugreifen willst, musst du dann also dort BeginInvoke verwenden. Der BackgroundWorker hat etwas mehr Overhead beim Erzeugen, dafür laufen die EventHandler automatisch im GUI-Thread ab.

Wie macht ihr das eigentlich mit den Invokes? Macht ihr generell alle Forms und
Controls Thread-Safe oder wählt ihr nur selektiv die aus, von denen ihr auch
wisst, dass ihr sie im Thread aufrufen werdet? Langfristig sicherer wäre ja
ersteres...

Dafür gibt es "Multithreaded Programming with the Event-based Asynchronous Pattern". Die Methoden der Klasse würde also intern asynchron arbeiten und der Aufrufer registriert einen EventHandler, um mitzukriegen, ob die Methode fertig ist. Die Klasse könnte auch einen Event für Fortschrittsmeldungen definieren. Das würde also auch deine Forderungen erfüllen. Allerdings habe ich heute so ziemlich den ganzen Tag gebraucht, um diesen Pattern zu verstehen. Und ich denke, ich kenn mich einigermaßen aus. Einsteiger wären hier auf jeden Fall überfordert.

Danke für den Verweis auf das Pattern. Werd ich mich mal reinfuchsen. Nach
erstem durchlesen, ist es ja wirklich genau dass was ich will. Bin zwar bei C#
noch nicht lange dabei, aber programmier schon etwas länger. Insofern
schockt mich auch keine schwere Kost 😉 Blöd ist nur immer mehrere Verfahren
bezüglich ihrer Tauglichkeit einzuschätzen, ohne mit ihnen Erfahrungen gemacht
zu haben.

Gruß,

inTrance

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo inTrance,

Macht ihr generell alle Forms und Controls Thread-Safe oder wählt ihr nur selektiv die aus, von denen ihr auch wisst, dass ihr sie im Thread aufrufen werdet?

nur bei Bedarf. Oft mache ich es sogar so, dass BeginInvoke nur aus Threads heraus verwende. Also der Thread sorgt dann dafür, dass seine Aufrufe safe sind und nicht das Form; das spart auch das InvokeRequired. Mit dem BachgroundWorker und mit dem o.g. "Event-based Asynchronous Pattern" bekommt man allerdings ein Modell, das für Konsolen-, Forms- und Web-Anwendungen gleichermaßen tauglich ist. Allerdings kostest das im Fall des Pattern leider auch entsprechend Aufwand.

Du kannst ja mal TreeView von Thread aktualisieren lassen und die Folgenden Beiträge angucken. Dort werden die beiden von die genannten Ansätze gegenübergestellt.

herbivore

inTrance Themenstarter:in
170 Beiträge seit 2005
vor 18 Jahren

Danke noch mal für die Tipps. Ich hab mich jetzt mal durch das "EbA" Pattern
durchgearbeitet und finde es schon mal gut, da es immerhin einen standardisierten
Weg zeigt, Threading zu implementieren.

Ganz sicher, bin ich mir noch nicht, ob ich AsyncOperation.PostOperationCompleted
richtig verstanden habe. Wenn ich richtig liege, führt die Methode einfach ein
PostMessage durch, das dann ja sowieso im GUI-Thread abgearbeitet wird?

Was mir nicht gefällt ist, dass der Thread dadurch ja auch "weiß", dass er eine
GUI-Operation ausführt. Denn ansonsten wäre der Schnickschnack mit Post ja
nicht notwendig. Genauso wäre es ja auch, wenn man das BeginInvoke innerhalb
des Threads ausführt.... Hm.... Als wie "abstraktionsschädigend" schätzt ihr das ein?
Ich meine es kann ja eigentlich keinen Schaden anrichten, wenn man das FrontEnd
wechselt... (z.b. zu Konsole oder Web).

Ach ja noch eine Verständnis-Frage: Ein delegate.BeginInvoke führt eine Operation
auf einem beliebigen Thread aus dem Pool aus, während ein Control.BeginInvoke
die Methode auf dem GUI-Thread ausführt, oder?

P.S: Kann man die Offline MSDN eigentlich aktualisieren? Die ganzen Asynchronous-
Klassen sind in der Offline-Version ja noch gar nicht vorhanden. Die online Variante
empfinde ich als zu träge - neben der grottigen Darstellung durch Opera X(

Würde mich freuen, wenn mir noch wer über die letzten Hürden hinweg helfen
würde 🙂

564 Beiträge seit 2006
vor 18 Jahren

Hi!

Bin selber auch noch nicht solange dabei und habe mich vor kurzem ein bißchen zu Threads in C# schlau gemacht.
Von meinem Verständnis her würde ich meinen, dass BeginInvoke immer eines tut: Daten von einem Thread im Prinzip in einen anderen Thread schaufeln, wobei es der Methode egal ist welche Threads das nun sind.
Bei Control.BeginInvoke willst du ja auf das Control zugreifen, und das befindet sich für gewöhnlich im GUI-Thread...
Bitte berichtigen, wenn ich hier noch was falsch sehe! 🙂

MSDN-Online ist tatsächlich recht träge, was daran liegen dürfte, dass so viele Entwickler darauf zurückgreifen. 🙂 Ein "Aktualisieren" kenne ich nicht, da dies eine Unmenge an Daten umfassen würde (glaube mindestens 2GB), und die Informationen sowieso nur eine gewisse Halbwertszeit aufweisen, womit Aufwand und Nutzen in keiner vernünftigen Relation zueinander stehen.

Gruß, der Marcel

:] 😄Der größte Fehler eines modernen Computers sitzt meist davor 😁 :]

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo

Ganz sicher, bin ich mir noch nicht, ob ich AsyncOperation.PostOperationCompleted richtig verstanden habe. Wenn ich richtig liege, führt die Methode einfach ein PostMessage durch, das dann ja sowieso im GUI-Thread abgearbeitet wird?

Kein, kein PostMessage, sondern ein SynchronizationContext.Post. Und das geht nicht zwangsläufig zum GUI-Thread (weil es den ja nur bei WindowsForms gibt), aber zum richtigen Thread.


public void PostOperationCompleted(SendOrPostCallback d, object arg)
{
      this.Post(d, arg);
      this.OperationCompletedCore();
}

public void Post(SendOrPostCallback d, object arg)
{
      this.VerifyNotCompleted();
      this.VerifyDelegateNotNull(d);
      this.syncContext.Post(d, arg);
}

Was mir nicht gefällt ist, dass der Thread dadurch ja auch "weiß", dass er eine GUI-Operation ausführt. Denn ansonsten wäre der Schnickschnack mit Post ja nicht notwendig.

Nein, das stimmt nicht. Das Post postet immer an den richtigen Thread, aber nicht zwangläufig an den GUI-Thread (weil es den ja nur bei WindowsForms gibt).

Genauso wäre es ja auch, wenn man das BeginInvoke innerhalb des Threads ausführt....

Bei BeginInvoke hast du im Prinzip recht. Aber ich nutze es da ja auch nur für Thread, die zum GUI gehören, wie z.B. beim Füllen den TreeViews. Ich würde in einer Modellklasse nicht BeginInvoke verwenden. Und das würde ja schon alleine deshalb auffallen, weil man "plötzlich" den Windows.Forms-Namespace braucht.

Hm.... Als wie "abstraktionsschädigend" schätzt ihr das ein?

In dem beschriebenen Sinne gar nicht.

Ich meine es kann ja eigentlich keinen Schaden anrichten, wenn man das FrontEnd

Wenn man es in der Modellklasse verwenden schon, weil man die Modellklassen dann ohne Änderung nicht mehr z.B. für Konsolen-Anwendungen verwenden kann.

Ach ja noch eine Verständnis-Frage: Ein delegate.BeginInvoke führt eine Operation auf einem beliebigen Thread aus dem Pool aus, während ein Control.BeginInvoke die Methode auf dem GUI-Thread ausführt, oder?

Kann man so sagen.

herbivore

inTrance Themenstarter:in
170 Beiträge seit 2005
vor 18 Jahren

Hab mir jetzt mal für die einfachste Form des Pattern (kein Status-Reporting, keine
Differenzierung von mehreren Callern) ein Snippet geschrieben, dass eine Async
Methode für eine gegebene synchrone Methode erstellt. Vielleicht will es sich ja
mal wer durchsehen?

Hab auch mal Exception Handling eingebaut, weiß aber nicht, ob es so korrekt
gemacht ist. Mein Wunsch war es die Exceptions aus dem Thread heraus in die
Hauptanwendung zu bringen. Geht mein Konzept mit dem Exception-Event auf?
// Hab gerade gesehen Excepted gibts gar nicht im Englischen 😁

Hier einnmal angwendet auf die Methode "Calc" und dann noch mal als Anhang
das richtige Snippet:

public void Calc()
{
    System.Threading.Thread.Sleep(5000);
    throw new Exception("bla?");
}

#region Asynchronous Method for "Calc" and support code

#region private members

/// <summary>
/// Delagate for calling CalcCaller
/// </summary>
private delegate void CalcCallerEventHandler(AsyncOperation asyncOp);
private CalcCallerEventHandler CalcCallerDelegate;

#endregion

#region Events

/// <summary>
/// Completed Event
/// </summary>
public delegate void CalcCompletedEventHandler(object sender, EventArgs e);
public event CalcCompletedEventHandler CalcCompleted;

/// <summary>
/// Excepted Event
/// </summary>
public delegate void CalcExceptedEventHandler(object sender, ExceptionEventArgs e);
public event CalcExceptedEventHandler CalcExcepted;

#endregion

/// <summary>
/// Asynchronous version of Calc
/// Calls Calc in a new thread from the thread pool
/// </summary>
public virtual void CalcAsync()
{
    AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

    CalcCallerDelegate = new CalcCallerEventHandler(CalcCaller);
    CalcCallerDelegate.BeginInvoke(asyncOp, null, null);
}

#region Support methods

/// <summary>
/// Calls Calc
/// Calc must be thread-safe
/// </summary>
private void CalcCaller(AsyncOperation asyncOp)
{
    try
    {
        Calc();
    }
    catch (Exception error)
    {
        ExceptionCalc(asyncOp, error);
        return;
    }

    CompletionCalc(asyncOp);
}

#region Exception handling

/// <summary>
/// ExceptionCalc
/// </summary>
private void ExceptionCalc(AsyncOperation asyncOp, Exception exception)
{
    try
    {
        asyncOp.Post(new SendOrPostCallback(CalcExceptedCaller), exception);
    }
    catch
    { }
}

/// <summary>
/// CalcExceptedCaller
/// </summary>
private void CalcExceptedCaller(object exception)
{
    OnCalcExcepted(new ExceptionEventArgs(exception as Exception));
}

/// <summary>
/// ExceptionEventArgs class
/// Contains only an exception object
/// </summary>
public class ExceptionEventArgs : EventArgs
{
    public ExceptionEventArgs(Exception exception)
    {
        this.exception = exception;
    }

    private Exception exception;
    public Exception Error
    {
        get { return exception; }
    }
}

#endregion

#region Completion handling

/// <summary>
/// CompletionCalc
/// </summary>
private void CompletionCalc(object state)
{
    AsyncOperation asyncOp = state as AsyncOperation;

    asyncOp.PostOperationCompleted(new SendOrPostCallback(CalcCompletedCaller), null);
}

/// <summary>
/// CalcCompletedCaller
/// </summary>
private void CalcCompletedCaller(object state)
{
    OnCalcCompleted(new EventArgs());
}

#endregion

#endregion

#region event Methods

/// <summary>
/// OnCalcCompleted
/// </summary>
protected void OnCalcCompleted(EventArgs e)
{
    if (CalcCompleted != null)
    {
        CalcCompleted(this, e);
    }
}

/// <summary>
/// OnCalcExcepted
/// </summary>
protected void OnCalcExcepted(ExceptionEventArgs e)
{
    if (CalcExcepted != null)
    {
        CalcExcepted(this, e);
    }
}

#endregion

#endregion
49.485 Beiträge seit 2005
vor 18 Jahren

Hallo inTrance,

ich gucke es heute Abend mal durch. Vorneweg:

Mein Wunsch war es die Exceptions aus dem Thread heraus in die
Hauptanwendung zu bringen. Geht mein Konzept mit dem Exception-Event auf?

Klar kannst du das machen. Dafür gibt es ja den Exception Parameter in den ...EventArgs.

herbivore

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo inTrance,

das einzige, was mir aufgefallen ist, ist das mit den Event/EventArgs für die Fertigstellungsmeldung. Du solltest die AsyncCompletedEventArgs benutzen und diese bei PostOperationCompleted auch übergeben. Außerdem soll für Gut- und Fehlerfall PostOperationCompleted verwendet werden. Der Unterschied ist lediglich, dass AsyncCompletedEventArgs.Exception einmal null und einmal != null ist. Weitere Unterschiede für Gut- und Fehlerfall soll es nicht geben. Also vor allem auch keine zwei Events, sondern nur einen.

herbivore