Einzelnen Beitrag anzeigen

berens

Registriert seit: 3. Sep 2004
441 Beiträge
 
Delphi 10.4 Sydney
 
#7

AW: Sind alle Thread Variablen in OnTerminate threadsicher?

  Alt 24. Feb 2025, 22:31
Danke schonmal für den guten Input.

TTask kannte ich tatsächlich bisher nicht.

Im Prinzip kann ich nun einfach meine alten Testprozeduren (fast!) 1:1 übernehmen, indem ich sie einfach mit TTask.Run umgebe.
Klar, auch hier muss ich wieder auf die Threadsicherheit achten, aber der ganze Overhead entfällt tatsächlich komplett.

Delphi-Quellcode:
TTask.Run(
  procedure
  var
    Wert: Integer;
  begin
    // Siehe Hinweis 1, hier im Beispiel ignorieren!!!
    SelfRef := Self; // Sicherstellen, dass wir auf die Instanz zugreifen können

    Wert := Random(100);
    TThread.Queue(nil,
      procedure
      begin
        ShowMessage('Ergebnis: ' + IntToStr(Wert));
        // Konkret in meinem Beispiel:
        Self.ExternerWert := Wert;
      end);
  end);
Nur noch eine Frage: ChatGPT hat (wahrscheinlich nicht ganz zu Unrecht) recht große Bedenken, dass "Self" bis zum Ende des Tasks nicht mehr existiert. Hier in dem Beispiel unwahrscheinlich, in der Praxis mit der Bambusleitung über VPN nicht zu 100% ausschließbar, dass der Benutzer das Programm wieder schließt, bevor der TTask beendet ist. ChatGPT will hier unbedingt Self in "SelfRef" zwischenspeichern, und dann auch unten SelfREF.ExternerWert := Wert; verwenden. Meiner Meinung nach rettet dies das Programm auch nicht mehr vor dem Absturz, wenn ich im Hauptprogramm schon die dazugehörige Komponente mit "FreeAndNil" freigegeben habe. Was tun? Einfach ExternerWert := Wert; (ohne Self oder SelfREF) zuweisen und das Beste hoffen, oder akuter Handlungsbedarf?

"Zur Güte" habe ich nun noch versucht, die Zuweisungen an Self über eine Callback-Prozedur zu lösen, damit ich nicht vom TTask aus (trotz TThread.Queue!) direkt auf die Komponenten-Variablen zugreifen muss. Unnötig? Dringend erforderlich?

Ich bin auch sehr unglücklich, dass ich kein "Early Exit" mehr verwenden kann. Schließlich muss auch, wenn alle Vorbedingungen fehlschlagen, am Schluss der Wert ans Hauptprogramm mit TThread.Queue übergeben werden. Ich fände das jetzt relativ blöd und schwer zu warten bei jedem nicht bestandenen "if" mit " TestComplete(False, FUserName, FDomain, FVollbild); Exit;" das zu jeder Anweisung zu schreiben. Wäre aber wahrscheinlich dennoch sinnvoller als dieser Quatsch mit dem zwischengespeicherten Status "bl", der erst ganz am Schluss an "blOkay" übergeben wird.

Delphi-Quellcode:
procedure TSelbsthilfe_AutoLogon.Test;
const
  P = 'Test';
begin
  try
    TTask.Run(procedure
    var
      bl, blOkay: Boolean;
      tmpRegistry: TRegistry;
      FVollbild: Boolean;
      FResultText: string;
      FUserName, FDomain: string;
    begin
      tmpRegistry := NIL;
      try
        FUserName := '';
        FDomain := '';
        FResultText := '';

        // Vollbildmodus auslesen
        FVollbild := HAL_Registry_GetBool('blFullScreen', False);
        if FVollbild then begin
          blOkay := False;

          tmpRegistry := TRegistry.Create(KEY_ALL_ACCESS OR KEY_WOW64_64KEY);
          try
            tmpRegistry.RootKey := HKEY_LOCAL_MACHINE;

            bl := tmpRegistry.OpenKeyReadOnly('Software\Microsoft\Windows NT\CurrentVersion\Winlogon');
            if not bl then begin
              FResultText := _('Fehlgeschlagen: Status der automatische Anmeldung konnte ausgelesen werden.');
            end;

            // AutoLogon-Option auslesen
            if bl and (not tmpRegistry.ValueExists('AutoAdminLogon')) then begin
              FResultText := _('Fehlgeschlagen: Automatische Anmeldung nicht aktiv: Wert existiert nicht!');
              bl := False;
            end;

            if bl and (tmpRegistry.ReadString('AutoAdminLogon') <> '1') then begin
              FResultText := _('Fehlgeschlagen: Automatische Anmeldung nicht aktiv: Wert nicht wie erwartet!');
              bl := False;
            end;

            // AutoLogon-Benutzername auslesen
            if bl and (not tmpRegistry.ValueExists('DefaultUserName')) then begin
              FResultText := _('Fehlgeschlagen: Automatische Anmeldung nicht aktiv: Benutzername existiert nicht!');
              bl := False;
            end;

            if bl then begin
              FUserName := trim(tmpRegistry.ReadString('DefaultUserName'));
              if FUserName = 'then begin
                FResultText := _('Fehlgeschlagen: Automatische Anmeldung nicht aktiv: Benutzername ist leer!');
                bl := False;
              end;
            end;

            // AutoLogon-Domain auslesen
            if bl and (not tmpRegistry.ValueExists('DefaultDomainName')) then begin
              FResultText := _('Fehlgeschlagen: Automatische Anmeldung nicht aktiv: Domain existiert nicht!');
              bl := False;
            end;

            if bl then begin
              FDomain := tmpRegistry.ReadString('DefaultDomainName');
              if FDomain = 'then begin
                FResultText := _('Fehlgeschlagen: Automatische Anmeldung nicht aktiv: Domain ist leer!');
                bl := False;
              end;
            end;

            blOkay := bl;
            tmpRegistry.CloseKey;
          finally
            FreeAndNil(tmpRegistry);
          end;

        end else begin
          blOkay := True;
          FResultText := _('Erfolg: Dieser PC startet den Viewer nur im Vorschaumodus. Der Status der automatischen Windows-Anmeldung wurde nicht überprüft.');
        end;

        TThread.Queue(nil,
          procedure
          begin
            TestComplete(blOkay, FUserName, FDomain, FVollbild);
          end);
      except
        on E: Exception do begin
          TThread.Queue(nil,
            procedure
            begin
              FResultText := Format(_('Fehlgeschlagen: Fehler beim Auslesen der automatischen Anmeldung: "%s".'), [E.Message]);
              TestComplete(False, FUserName, FDomain, FVollbild);
            end);
        end;
      end;
    end);
  except
    on E: Sysutils.Exception do begin
      LogP(QualifiedClassName, P, '', E.Message, ws_SEVERITY_EXCEPTION);
    end;
  end;
end;

procedure TSelbsthilfe_AutoLogon.TestComplete(_AllesOK: Boolean; _Username, _Domain: string; _Vollbild: Boolean);
begin
  FAllesOk := _AllesOK;
  UserName := _Username;
  Domain := _Domain;
  Vollbild := _Vollbild;
  NotifyTestCompleted;
  if _Vollbild then begin
    Self.MyFrame.edtUserName.Text := _UserName;
    Self.MyFrame.edtDomain.Text := _Domain;
  end;
end;
PS: blOkay wird erst gegen Ende der Prozedur gesetzt, damit nicht das "bl=True" bei einer Exception als Rückgabewert gilt (blOkay wird also nur dann gesetzt, wenn alle Befehle ohne Exception ausgeführt werden, ansonsten bleibt es bei blOkay=False als Initialisierungswert). Ist wie ich gerade sehe unnötig, weil ich bei except eh bei FAllesOkay ein statisches "False" als Parameter zum Testobjekt zurückgebe. Naja, egal.


Meine wichtigsten Fragen nochmal zusammengefasst:
1) TThread.Queue( ... Self.ExternerWert := Wert; ...) - Problematisch? Ja oder Nein?
2) Zusätzliche Referenz auf Self? Quatsch oder Notwendig?
3) TThread.Queue( ... TestComplete(False, FUserName, FDomain, FVollbild); ... ) NOTWENDIG oder DARF ich auch direkt innerhalb von TTask.Run
Delphi-Quellcode:
  TThread.Queue(nil,
  procedure
  begin
    FAllesOk := blOkay;
    UserName := FUsername;
    Domain := FDomain;
    Vollbild := FVollbild;
    NotifyTestCompleted;
  end;
  end);
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
  Mit Zitat antworten Zitat