Announcement

Collapse
No announcement yet.

Wie funktioniert das mit der ThreadID

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Wie funktioniert das mit der ThreadID

    Hallo,

    ich schreibe eine Anwendung, in der ein Thread syncronisiert abläuft.

    In einem Methodenaufruf dieses Threads ist es wichtig festzustellen, ob der Aufruf aus dem Hauptprogramm oder dem Thread erfolgte.

    Meine Idee war, mittels dem Vergleich von ThreadID des Thread und GetCurrentThreadID den Aufrufer festzustellen, was jedoch leider nicht funktioniert, da GetCurrentThreadID immer den gleichen Wert hat, egal von wo die Methode aufgerufen wird.

    Hat vieleicht jemand eine Idee, wie ich das Problem anders lösen kann ?

    Für Hilfe bin ich sehr dankbar.

    Gruß Chris

  • #2
    Hallo Entwickler,

    da ich bis heute keine Reaktion auf mein Problem erhalten habe, will ich nun versuchen, dieses genauer darzustellen.

    Ich habe eine Klasse, abgeleitet von TThread, welche eine Liste (TThreadlist) mit Objekten abarbeitet, die alle eine Methode "DoExecute" haben.

    <PRE>
    procedure Execute;
    VAR Task: TTask;
    begin
    while not terminated do begin
    Task := NextTask; // Liste wird cyclich abgearbeitet
    if assigned(Task) then
    syncronisize(Task.DoExecute)
    else
    Suspend;
    end;
    end;
    </PRE>

    In der DoExecute-Methode kann sich der Status des Objects "Task" ändern (ts_starting, ts_running, ts_terminated).

    Nun gibt es eine Methode WaitForTask(aTask: TTask); in der solange mit Ausführung des weiteren Programm's gewartet werden soll, bis der Status des übergebenen TTask = ts_terminated ist. Wird diese Methode aus dem Hauptprogramm aufgerufen ist die Sache recht einfach:

    <PRE>
    while aTask.Status <> ts_terminated do
    Application.processmessages;
    </PRE>

    Es kann aber auch vorkommen, das diese Methode aus der DoExecute-Methode eines anderen Taskes heraus aufgerufen wird womit die Anwendung in einer Endlosschleife landed, da ja der Task, auf welchen gewartet wird, nie Rechenzeit bekommen wird. Daher muß ich in der Methode WaitForTask unterscheiden, ob diese Methode aus dem eigen Thread oder aus einem anderen heraus aufgerufen wird, und genau hier ist mein Problem.

    Ich habe mir so geholfen, das ich in der Execute-Methode des Threads mit jedem Durchlauf einen Zähler incrementiere und beim Eintritt in die Methode WaitForTask zunächst max. 25 ms lang prüfe, ob sich dieser Zähler ändert. Wenn ja erfolgete der Aufruf aus einem anderen Thread. Dies funktioniert soweit gut, ich habe aber die Sorge, das diese Lösung stark von der Rechnerleistung und dem in DoExecute imlementierten Code abhängig ist, was mir Bauchschmerzen bereitet.

    Vieleicht hat ja doch jemand eine Idee, wie man dieses Problem besser und sicherer lösen kann.

    Gruß
    Chri

    Comment


    • #3
      Hallo,

      die Win32-API-Funktion GetCurrentThreadID liefert die Thread-ID des Threads zurück, aus dem diese API-Funktion aufgerufen wurde. Somit kann auf diesem Weg immer nur die "eigene" Thread-ID ermittelt werden.

      Wenn ein anderer Thread immer nur warten soll und wenn man den Aufwand einer Synchronisation vermeiden will, kann man auch anstelle des Threads zu einem <b>Fiber</b> greifen (ab Windows NT 4 aufwärts).

      P.S: Wenn die Thread-Nutzfunktion immer nur über Synchronize gestartet wird, verstehe ich nicht ganz, welchen Sinn diese Threads dann haben sollen

      Comment


      • #4
        Hallo Herr Kosch,

        zunächst mal danke für die Info. Wenn ich das richtig verstanden habe, liefert mir die Win32-API-Funktion GetCurrentThreadID immer die ID des Haupt-Threads, da ich die Nutzfunktion über Synchronize gestartet habe. Ich hatte dies auch schon vermutet.

        Zum Verständnis, was ich eigentlich erreichen möchte, hier einige Infos zu meinem Projekt.
        Ziel ist es, eine Anwendung zu erstellen, in die eigentlichen Programmfunktionen in BPL-Dateien ausgelagert sind. Die Hauptanwendung bildet nur noch die Plattform für diese Clients. Hierzu ist in der Hauptanwendung eine Komponente THost, welche die Verwaltung, das Starten und Anzeigen und die Steuerung der Clients übernimmt. Die BPL-Dateien befinden sich alle in einem bestimmten Unterverzeichnis, das heist, die Funktionalität der Anwendung wird durch die BPL-Dateien in diesem Unterverzeichnis bestimmt.
        Jede dieser BPL-Dateien registriert eine Klasse, welche von TClient abgeleitet wurde, wobei TClient widerum von TDataset abstammt.
        Diese Klasse bildet die Schnittstelle. Die Kommunikation und der Datenaustausch zwischen den Clients erfolgt immer über den Host und zwar über eine eindeutige ID, welche sich auch im Klassennamen widerfindet.
        Nun zu meinen Thread, ich nenn ihn mal Sheduler. In jedem Client lassen sich via Designer zur Entwurfszeit Jobs erzeugen. Ein solcher Job hat außer dem Namen 3 Events, und zwar "OnStart", "OnExecute" und "OnTerminate", wobei OnExecute eine Function ist, welche ein boolean zurückliefert. Der Sheduler terminiert einen Job immer dann, wenn das Ergebnis von OnExecute = false liefert.
        Der Host sorgt nun beim Laden des Clients für die Ablage der Jobs im Sheduler, sowie beim Zerstören des Clients, für deren Entfernen. Jeder Job kann mittels Start bzw. Stop gesteuert werden. Ferner kann über eine Egenschaft (NextStart: TDatetime) eine bestimmte Zeit festgelegt werden, wann der Job ausgeführt werden soll. Die Aufgaben dieser Jobs können recht vielfältig sein, so z.B. sorge ich in den Clients dafür, dass alle Datenbank-Operationen in den Jobs gekapselt sind. Damit ist sicher gestellt, das diese im gleichen Thread laufen, IB-Komponenten sind nun mal nicht threadsicher oder ich müsste ein Unzahl von Datenbankverbindungen aufrechterhalten, was sich nicht bewährt hat (viel zu langsam).
        Da ich nun nicht überschauen kann, welche Auswirkungen der Code im OnExecute Event der Jobs auf die VCL hat, habe ich den Thread "Sheduler" syncronisiert, alles andere hat leider nicht funktioniert. Natürlich ist mir klar, das damit die Jobs nicht wirklich in einem eigenen Thread laufen, ich erreiche damit aber zumindest, das meine Anwendung nicht einfriert und die Jobs ereignisgesteuert im Hintergrund ablaufen. Beim Design der Jobs achte ich immer darauf, das bei lägeren Operationen, die eigentliche Aufgabe in kleine Teile zerlegt wird, damit mögliche andere Jobs ebenfalls Rechenzeit erhalten. Hier ein typisches Beispiel:

        Der Client "CRM8213" sorgt für das Einlesen und Verarbeiten von Bewegungsdaten einer Zutrittskontrolle. Ein anderer Client holt die Datensätze von den Lesern der Zutrittskontrolle zeitgesteuert über die serielle Schnittstelle ab, und schickt die Liste mit Daten an diesen Client. Dadurch wird der WorkJob gestartet. Dieser arbeitet nun diese Liste ab, und erzeugt entsprechende Datensätze in einer Datenbanktabelle.

        <PRE>
        procedure TCRM8213.WorkJobStart(Sender: TObject; var allowed: Boolean);
        begin
        fEntry := fProtList.Pop; // Eintrag vom Stapel holen
        allowed := assigned(fEntry);
        fStep := 0;
        end;

        function TCRM8213.WorkJobExecute: Boolean;
        begin
        Result := true;
        case fStep of
        0: begin
        if TA.InTransaction then TA.commit;
        qryCtrl.Active := true;
        inc(fStep);
        end;
        1: if qryCtrl.Locate('ADRESSE', fEntry.fLeser, []) and (fEntry.fDaten.Count > 0) then begin
        if fEntry.fDaten.Count > 1 then
        EventLog := 'Verarbeite '+IntToStr(fEntry.fDaten.Count)+' Datensätze von Leser '+qryCtrlA

        Comment


        • #5
          Das war wohl zu lang, hier gehts weiter:

          <PRE>
          1: if qryCtrl.Locate('ADRESSE', fEntry.fLeser, []) and (fEntry.fDaten.Count > 0) then begin
          if fEntry.fDaten.Count > 1 then
          EventLog := 'Verarbeite '+IntToStr(fEntry.fDaten.Count)+' Datensätze von Leser '+qryCtrlAdresse.AsString+' ...'
          else
          EventLog := 'Verarbeite einen Datensatz von Leser '+qryCtrlAdresse.AsString+' ...';
          fIndex := 0;
          inc(fStep);
          end else
          Result := false;
          2: if fIndex < fEntry.fDaten.Count then begin
          qryPersonal.Active := true;
          ibdBewegung.Active := true;
          if qryPersonal.Locate('CODE', Code, []) then begin
          if not ibdBewegung.Locate('USRID;CTRLID;ZEITPUNKT',
          VarArrayOf([qryPersonalID.asInteger,qryCtrlID.AsInteger,ZEITPU NKT]), []) then begin
          ibdBewegung.Append;
          ibdBewegungZEITPUNKT.AsDateTime := Zeitpunkt;
          ibdBewegung.Post;
          end;
          end;
          inc(fIndex);
          end else
          Result := false;
          else Result := false;
          end;
          end;

          procedure TCRM8213.WorkJobTerminate(Sender: TObject;
          var newstart: Boolean);
          begin
          FreeAndNil(fEntry);
          newstart := Workjob.Restart;
          // wenn zwischenzeitlich eine neue Liste angekommen ist und damit ein WorkJob.Start erfolgte, ist newstart = true und der Job wird sofort wieder gestartet.
          end;
          </PRE>

          Meine Testanwendung ist im Betrieb und setzt sich zur Zeit aus 43 solcher Clients mit bis zu 268 Jobs zusammen, je nachdem wieviele Clients gerade geladen sind. Das funktioniert alles einwandfrei, mein einzges Problem ist, das ich unsicher bin, wie sich die Sache auf anderer Hardware und unter anderen Rahmenbedingungen (Netzwerk, ect.) verhällt, da die zuvor beschriebene Methode mit dem Zähler und den 25 ms mir doch sehr unsicher erscheint.

          Für Hinweise und Tips bin ich dankbar.

          Gruß Chris

          PS: Von "Fiber" habe ich noch nie was gehört, werde aber sofort mal versuchen mich kundig zu machen

          Comment

          Working...
          X