Entwickler-Ecke

Open Source Units - TFiber - Fiber Klasse für Delphi


delfiphan - Fr 04.09.09 20:27
Titel: TFiber - Fiber Klasse für Delphi
(Aus diesem Post [http://www.delphi-forum.de/viewtopic.php?t=94449] entstanden)
TFiber

Die Klasse TFiber stellt eine separate Ausführungsumgebung zur Verfügung, die unabhängig von Threads existiert. Diese hat einen eigenen Satz von Registers, Stack, Exception Chain und Instruction Pointer.

Im Gegensatz zu einem Thread wird ein Fiber nicht vom Betriebsystem verwaltet und ausgeführt, sondern muss manuell in einem Thread der Wahl ausgeführt werden. Daher sieht das Starten eines Fibers erst mal aus wie ein normaler Funktionsaufruf. Der Vorteil ist aber, dass die Ausführung eines Fibers an einer beliebigen Stelle gestoppt und zu einem späteren Zeit forgesetzt werden kann. Ausserdem ist es möglich, mit einem einfachen Funktionsaufruf an beliebiger Stelle im Kontrollfluss ein Fiber von einem Thread auf einen anderen zu transferieren.

Anwendungsfälle
- Fibers können verwendet werden, um an beliebiger Stelle während der Ausführung den Thread zu wechseln, bspw. um den MainThread freizugeben.

- Fibers können verwendet werden, um CoRoutinen zu implementieren.

- Fibers können die Anzahl Threads reduzieren, wenn sehr viele Ausführungspfade existieren, diese aber nicht ständig laufen. So kann der Fiber inaktiv sein, bis wieder Arbeit ansteht, um dann wieder in einem Thread aus einem Threadpool ausgeführt zu werden.

Features
- Transparentes Exception Handling über Thread-Grenzen hinweg.
- "Inline" Wechsel zwischen MainThread und Worker-Thread aus einem ThreadPool
- Direkter Transfer von einem Fiber in einen anderen möglich

Methoden von TFiber
- SwitchToWorkerThread: Ausführung in einem WorkerThread fortführen
- SwitchToMainThread: Ausführung im MainThread fortführen
- Yield: Kontrolle an Thread zurückgeben
- Transition: Kontrolle an anderen Fiber übergeben
- Resume: Kontrolle von Thread an Fiber übergeben

Properties von TFiber
- FreeOnTerminate: Instanz automatisch freigeben, wenn der Ausführungspfad des Fibers abgeschlossen ist.

Die Units mit Beispielprojekt im Anhang oder hier [http://www.tyberis.com/download/Fibers.zip]. Falls jemand nach einer ThreadPool Implementierung sucht ist hier eine mit dabei.

Beispiel

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TForm1.Button1Click(Sender: TObject);
begin

  // Rerun this method in a fiber
  if TEventFiber.RerunInFiber(Self, Button1Click) then
    Exit;

  try
    TFiber.Current.SwitchToWorkerThread;

    // Code here runs in a worker thread

  finally
    TFiber.Current.SwitchToMainThread;
  end;

  // Code here runs in the main thread again

end;


// Edit: Race condition fixed


delfiphan - Fr 02.04.10 16:42

Die Library scheint niemand zu mögen ;) habe sie jetzt noch weiter vereinfacht.

Bei Events vom Typ TNotifyEvent muss man jetzt nur noch TEventFiber.RerunInFiber ausführen, um die Vorteile von Fibers nutzen zu können. Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure TForm1.Button1Click(Sender: TObject);
begin

  // Diese Methode in einem Fiber starten
  if TEventFiber.RerunInFiber(Self, Button1Click) then
    Exit;

  try
    // Schnell vom Mainthread in einen Workerthread wechseln
    TFiber.Current.SwitchToWorkerThread;

    // ...
    // hier eine Berechnung, die lange dauert (läuft in einem Thread)
    // ...

  finally
    // Und wieder zurück in den Mainthread
    TFiber.Current.SwitchToMainThread;
  end;

end;


alzaimar - Fr 02.04.10 17:41

Das ist zu hoch für 99.9% aller Programmierer.


Martok - Fr 02.04.10 17:58

Nicht unbedingt zu hoch, aber sowas ohne echte Sprachfeatures zu machen, ist irgendwie... unschön.

Vielleicht könnte man da mit den DLangExtensions was machen?


Webo - Fr 02.04.10 19:01

Jetzt, wie ich mir den Eingangstext so durchgelesen habe scheint das ein sehr interessantes Thema zu sein. Auch wenn ich direkt nicht weiß, wo ich sowas einbauen könnte, ich werde gleich mal damit rumspielen. Mal schauen was sich so ergibt 8)


delfiphan - Fr 02.04.10 19:03

Ich habe diese Library verwendet, um bestehenden Code multi-threaded zu machen. Z.B. wechsle ich bei langen DB-Queries schnell in einen WorkerThread (Achtung, die DB-Library muss natürlich Thread-Safe sein!). Wenn die Query fertig ist, geht es wieder weiter mit dem bestehenden Kontrollfluss im MainThread. Interessant auch bei blockierenden Calls bei Indy-Komponenten.

Kompliziert ist das ganze nicht, zu mindest nicht in der Anwendung. Im Gegenteil, es soll Threading vereinfachen.

Wenn man mit TThread was machen will, muss man ja:
- Von TThread ableiten. Der Code, der im Thread läuft in eine Execute-Methode packen.
- Informationen, die der Thread braucht muss man separat übergeben z.B. über public Properties in der abgeleiteten Klasse.
- Um die Kontrolle zurück an den MainThread zu geben muss man Synchronize verwenden. Die nimmt wieder nur Methoden ohne Argumente entgegen. Man muss wieder mit irgend welchen privaten Variablen arbeiten, die vielleicht nur temporärer Natur sind.
- Bei Exceptions im Thread muss man die selbst wieder an den MainThread kommunizieren.

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
sowas ohne echte Sprachfeatures zu machen, ist irgendwie... unschön.

Da gebe ich dir Recht. Ich würde so eine Funktionalität auch nicht in die RTL/VCL aufnehmen, wenn ich bei Borland arbeiten würde. Aber praktisch finde ich die Library trotzdem.


Webo - Fr 02.04.10 19:31

user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
Kompliziert ist das ganze nicht, zu mindest nicht in der Anwendung. Im Gegenteil, es soll Threading vereinfachen.

Das ist wohl war. Ich habe grade mal ein bisschen getestet und muss sagen: Werde ich jetzt öfters anwenden. Ist ja ein Kinderspiel damit, Sachen in Threads auszulagern ;-)


Delete - Fr 02.04.10 19:51

user profile iconWebo hat folgendes geschrieben Zum zitierten Posting springen:
user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
Kompliziert ist das ganze nicht, zu mindest nicht in der Anwendung. Im Gegenteil, es soll Threading vereinfachen.

Das ist wohl war. Ich habe grade mal ein bisschen getestet und muss sagen: Werde ich jetzt öfters anwenden. Ist ja ein Kinderspiel damit, Sachen in Threads auszulagern ;-)

Vorsicht! Fibers sind keine Threads!
http://www.michael-puff.de/Developer/Delphi/Tutorials/Threads/HTML/Threads_mit_Delphich9.html#x38-540009


Webo - Fr 02.04.10 20:27

user profile iconLuckie hat folgendes geschrieben Zum zitierten Posting springen:
Vorsicht! Fibers sind keine Threads!

Geht klar Meister :flehan: (:)) ... dann ist es eben "ein Kinderspiel, Sachen in Pseudo-Threads auszulagern". Nee, Spaß beiseite, danke der Richtigstellung, man will ja dazu lernen.
Ob Pseudo-Thread oder nicht, aufjedenfall eine sehr nette Sache.


delfiphan - Fr 02.04.10 20:49

Fibers sind keine Threads, das wurde hier nirgends behauptet. Wenn ich oben von Threads oder Threading spreche, meine ich auch Threads.

In dieser Library geht es darum, mittels Fibers Threading zu vereinfachen. Die Vereinfachung resultiert daraus, dass man jeweils selbst wählen kann, in welchem Thread ein bestimmter Fiber läuft und den Thread auch jederzeit wechseln kann.

Diese Library enthält TFiber, eine Klasse, die Fibers implementiert. Diese, zusammen mit anderen Klassen in der Library, vereinfachen Threading.


Delete - Fr 02.04.10 21:27

user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
Fibers sind keine Threads, das wurde hier nirgends behauptet.

Dich meinte ich ja auch gar nicht, sondern Webo.


delfiphan - Sa 30.10.10 09:43

Hab die API noch ein wenig angepasst für Leute mit einer neueren Version von Delphi. Man kann einen Fiber nun als anonyme Methode von irgendwo aus starten.

Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
procedure TForm1.Button1Click(Sender: TObject);
begin

  TFiber.Run(
    procedure
    var
      I: Integer;
    begin
      (Sender as TButton).Caption := 'Calculating...';

      TFiber.Current.SwitchToWorkerThread;
      try
        // slow operation here
        for I := 0 to 99 do
          Sleep(100);
      finally
        TFiber.Current.SwitchToMainThread;
      end;

      (Sender as TButton).Caption := 'Calculation completed';
    end
  );

end;


BenBE - So 31.10.10 00:47

Sollte das Switchen zwischen MainFiber und TaskFiber nicht von der Fiber-Klasse realisiert werden? Also zumindest für die Initialisierung und Finalisierung des Fibers?


bummi - So 31.10.10 11:11

Interessante Klasse, ich werde mal Versuchen das ganze zu verstehen.


delfiphan - So 31.10.10 13:43

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Sollte das Switchen zwischen MainFiber und TaskFiber nicht von der Fiber-Klasse realisiert werden? Also zumindest für die Initialisierung und Finalisierung des Fibers?

Ich versteh nicht ganz; es gibt (im Beispiel oben) nur einen Fiber. Der Fiber wird dann auf dem einen oder anderen Thread ausgeführt.

Ich hab jetzt die API für die 2010er Version vereinfacht, sodass es nur noch 1 Klasse gibt (TFiber).

Zum Ausführen in einem Fiber:
TFiber.Run(<anonyme Methode>)

Innerhalb eines Fibers kann über TFiber.Current auf die aktuelle Instanz zugegriffen werden.


BenBE - So 31.10.10 17:52

Mich hatten die SwitchToMainThread und SwitchToWorkerThread-Aufrufe etwas irritiert. Daher die Frage.


delfiphan - So 31.10.10 18:10

SwitchToMainThread und SwitchToWorkerThread wird ja von der Fiber-Klasse realisiert. Die Methoden werden von der aktuellen Fiber-Instanz ausgeführt. Die aktuelle Instanz wird über TFiber.Current abgerufen. (Die ehem. TAnonymousFiber-Klasse leitete von TFiber ab, daher hat TFiber.Current die TAnonymousFiber-Instanz zurückgegeben. Die Klasse gibt's jetzt aber nicht mehr).

Sieht vielleicht auf den ersten Blick ungewöhnlich aus, aber schlussendlich spart es Tipparbeit. Mit der Klasse kann ich "freihändig" Fibers und Threads nutzen, d. h. ich muss weder eine Variable deklarieren noch eine Klasse implementieren. Um das Beispiel oben mit TThread zu realisieren, müsste man zuerst eine Klasse schreiben (von TThread ableiten), den Button als Feld zuweisen, die Methode Execute overriden und darin am Schluss wieder Synchronize aufrufen, um den Button-Text wieder anzupassen. Das ganze Exception-Handling im Thread kommt noch hinzu. Viel zu viel Aufwand für Implemenation, Testen und Wartung; ausserdem ist der Code nicht wirklich lesbarer. (bei dieser Argumentation müsste man sich aber fragen, ob Delphi die richtige Sprache dafür ist).


Fiji - So 27.07.14 14:32

It seems it will not work with 64 bit.