Autor Beitrag
Seifriz
Hält's aus hier
Beiträge: 8

Win 7 - 64 Bit
Delphi 6 Prof., Delphi 2007 Win32
BeitragVerfasst: Di 04.09.07 22:05 
Hallo,
ich versuche mich gearde zum ersten Mal an dem Einsatz von Threads.

Mein Ziel:
Ich möchte remote bei 20 PC's eine Console öffnen (mit sysinternals psexec.exe) und den Output der Console (Func. habe ich im WWW gefunden) auswerten!
Funktioniert alles prima wenn ich jeden PC hintereinander abfrage und auf den Output warte... das dauert mir aber zu lange, deshalb möchte ich den Aufruf der Console und den Output in einem Thread unterbringen... :wink:

Macht das Sinn?

Hier mein bisheriges Ergebnis:
Der Output der Console (Output bzw. Error) wird zum Test hier in zwei TMemo's angezeigt.
Leider ist das Ergebnis nicht wirklich gut. In dem Beispiel (... dir c:\ ...) werden oft nur die ersten paar Zeilen des Outputs angezeigt und nicht der komplette Inhalt von C:\. Liegt vermute ich an der Synchronisierung?!
Hat jemand einen guten Tip?

Danke!

ThreadObject VCL:
ausblenden volle Höhe 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:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
unit MyThreadObj;

interface

uses
  Classes,
  Windows;

type
  TCMD = class(TThread)
  private
    procedure DoCMD;
    function GetConsoleOutput(const Command: String; Output, Errors: TStringList): Boolean;
    procedure UpdateMainForm;
  protected
    procedure Execute; override;
  public
    Nummer: Integer;
    PCName: String;
    Benutzer: String;
    Passwort: String;
    Output: TStringList;
    Errors: TStringList;
  end;

implementation

uses
  Unit1;

procedure TCMD.Execute;
begin
  Output:=TStringList.Create;
  Errors:=TStringList.Create;
  DoCMD;
  Output.free;
  Errors.free;
end;


procedure TCMD.DoCMD;
begin
  if GetConsoleOutput('cmd /c psexec -u ' + PCName + '\' + Benutzer + ' -p ' + Passwort + ' -n 25 \\' + PCName + ' cmd /c "dir c:\"', Output, Errors) then
  begin
    Synchronize(UpdateMainForm);
  end
  else begin
         Output.Add('Error - GetConsoleOutput');
         Synchronize(UpdateMainForm);
       end;
  
end;

procedure TCMD.UpdateMainForm;
begin
  Form1.UpdateInMainForm(Output, Errors, Nummer, PCName, Benutzer, Passwort);
end;

function TCMD.GetConsoleOutput(const Command: String; Output, Errors: TStringList): Boolean;
begin
...
end;


und hier die Form1:
ausblenden volle Höhe 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:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,
  MyThreadObj;

type
  TForm1 = class(TForm)
    btnStart: TButton;
    btnStopp: TButton;
    Memo1: TMemo;
    Memo2: TMemo;
    procedure btnStartClick(Sender: TObject);
    procedure btnStoppClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    CMDoutput: Array[1..20] of TCMD;
  public
    procedure UpdateInMainForm(Output, Errors: TStringList; Nr: Integer; ComputerName, B, P: String);
  end;

var
  Form1: TForm1;
  PC, B, P: Array[1..20] of String;


implementation

{$R *.dfm}

procedure TForm1.UpdateInMainForm(Output, Errors: TStringList; Nr: Integer; ComputerName, B, P: String);
begin
  Memo1.Lines.AddStrings(Output);

  Memo2.Lines.AddStrings(Errors);
end;


procedure TForm1.btnStartClick(Sender: TObject);
var i:Integer;
begin
  Memo1.Clear;
  Memo1.Lines.Add('Threads gestartet!');

  for i:=1 to 20 do
  begin
    CMDoutput[i]:=TCMD.Create(True);
    CMDoutput[i].Nummer:=i;
    CMDoutput[i].PCName:=PC[i];
    CMDoutput[i].Benutzer:=B[i];
    CMDoutput[i].Passwort:=P[i];
    CMDoutput[i].Output:=nil;
    CMDoutput[i].Errors:=nil;    
    CMDoutput[i].Priority:=tpIdle;
    CMDoutput[i].FreeOnTerminate:=True;
    CMDoutput[i].Resume;
  end;
end;

procedure TForm1.btnStoppClick(Sender: TObject);
var i: Integer;
begin
  for i:=1 to 20 do
  begin
    CMDoutput[i].Terminate;
  end;

  Memo1.Lines.Add('Threads beendet!');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  PC[1]:='192.168.xxx.xx';
  B[1]:='Benutzer';
  P[1]:='Passwort';
  ...
end;

end.
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10185
Erhaltene Danke: 1261

W11x64
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Di 04.09.07 22:15 
Moin und :welcome: im Forum!

Zunächst mal gibt´s hier eine optimierte Version der Consolen-Funktion. ;)

Ich habe mir deinen Quelltext nicht soo genau angesehen, aber mir ist direkt aufgefallen, dass du alle Ausgaben in die selben Memos schreibst... :? das kann ja nicht gut gehen! :|

Ansatz: Mach ein Array of String jeweils für die Ausgabe und die Fehler und gibt die Strings geweils erst auf Anforderung ins Memo aus - oder mach mehr Memos auf die Form. :nixweiss:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
Seifriz Threadstarter
Hält's aus hier
Beiträge: 8

Win 7 - 64 Bit
Delphi 6 Prof., Delphi 2007 Win32
BeitragVerfasst: Di 04.09.07 22:34 
Hallo Narses,

danke für die schnelle Antwort.
Das mit dem einzigen Memo ist noch ein Relikt aus der "Nacheinander"-Version, da habe ich den Output wie in ein Log-File geschreiben.
Mehr Memos führen leider genau zum gleichen Ergebnis :(
Werde mir Dein optimierten Consolen-Output ansehen und eine Nacht darüber schlafen.

Danke
F.S.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 05.09.07 07:21 
Ein Memo für die Ausgaben mehrerer Threads zu verwenden, funktioniert tadellos, da der das Memo füllende Aufruf durch ein 'Synchronize' gekapselt ist und somit serialisiert ausgeführt wird.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
Procedure TMyThread.DoFillMemo; // Nie innerhalb des Threads direkt aufrufen!
Begin
  Form1.Memo1.Add(fMessage);
End;

Procedure TMyThread.FillMemo (Const aMessage : String);
Begin
  fMessage := aMessage; // fMessage ist ein privates Feld der TMyThread-Klasse;
  Synchronize (DoFillMemo);
End;

Wenn Du die Ergebnisse in einer TStringList zwischenspeichern willst, dann musst Du eben sicherstellen, das ein Thread exklusiven Zugriff auf die Liste hat. Das geht am Einfachsten mit einer 'Critical Section', die sich die Threads teilen. Diese kann z.B. global (ieek! global! Pfui!) in der Thread-Unit deklariert sein:

ausblenden 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:
25:
26:
27:
28:
29:
30:
Unit MyThreads;
interface
uses {unter anderem} Classes, SyncObjs;
Type
  TMyThread = Class (TThread)
    Procedure DoFillResult  (Const aMessage : String);
...
  End;
...
Var
  ThreadResults : TStringList;
  ExclusiveAccess : TCriticalSection;
...
Procedure TMyThread.DoFillResult  (Const aMessage : String);
Begin
  ExclusiveAccess.Enter;
  Try
    ThreadResults.Add (aMessage);
  Finally
    ExclusiveAccess.Leave;
  End
End;
...
Initialization
  ExclusiveAccess := TCriticalSection.Create;
  ThreadResults := TStringlist.Create;
Finalization
  ExclusiveAccess.Free;
  ThreadResults.Free;
End.

_________________
Na denn, dann. Bis dann, denn.
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10185
Erhaltene Danke: 1261

W11x64
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Mi 05.09.07 08:40 
Moin!

user profile iconalzaimar hat folgendes geschrieben:
Ein Memo für die Ausgaben mehrerer Threads zu verwenden, funktioniert tadellos,

Bitte beachten: "geht nicht gut" <> "funktioniert nicht"! :mahn:

Da er die Ausgaben "kommentarlos" in ein Memo klatscht, kann man sie nicht auseinanderhalten! :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 05.09.07 08:45 
user profile iconNarses hat folgendes geschrieben:
...:mahn:

:bawling: :flehan:


:wink:

_________________
Na denn, dann. Bis dann, denn.
Seifriz Threadstarter
Hält's aus hier
Beiträge: 8

Win 7 - 64 Bit
Delphi 6 Prof., Delphi 2007 Win32
BeitragVerfasst: Mi 05.09.07 10:02 
Danke alzaimar, danke Narses,

Ihr hängt Euch wirklich rein...
1. Narses: der optimierte Consolen-Output funktioniert und ist schneller - zumindest scheint mir das so. :D
2. Die Ausgabe in das Memo soll mir nur zeigen was ich zurück bekomme. Für die Auswertung müssen die Outputs natürlich getrennt und jeder für sich betrachtet werden... :wink:
3. Das mit der 'Critical Section' versteh ich nicht wirklich. Ich dachte ich hätte in jedem Thread je ein TSringList für den Output und die Errors :roll: und damit auch einen klaren Bezug (s.Code).

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
type
  TCMD = class(TThread)
  private
    procedure DoCMD;
    //function GetConsoleOutput(const Command: String; Output, Errors: TStringList): Boolean;
    function ExecConsole(const Command: String; var Output, Errors: String): Boolean;
    procedure UpdateMainForm;
  protected
    procedure Execute; override;
  public
    Nummer: Integer;
    PCName: String;
    Benutzer: String;
    Passwort: String;
    Output: TStringList;  // <<<<<<<<<<<
    Errors: TStringList;  // <<<<<<<<<<<
  end;


Ich habe aber immer noch das komische Phänomen, dass der Output von cmd /c dir c:\ nach 2 oder 3 Zeilen aufhört. Kann das nicht damit zusammenhänegn, dass Synchronize arbeitet, bevor der Output-String fertig aufgebaut ist :?!?:

Grüße
F.S.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 05.09.07 12:19 
Ach so:

user profile iconSeifriz hat folgendes geschrieben:

3. Das mit der 'Critical Section' versteh ich nicht wirklich. Ich dachte ich hätte in jedem Thread je ein TSringList für den Output und die Errors :roll: und damit auch einen klaren Bezug (s.Code).

Oho, hab ich übersehen. Dann brauchst Du das natürlich nicht. Critical sections verwendest Du dann, wenn Du eine Resource vor gleichzeitigem Zugriff unterschiedlicher Threads schützen must. Das ist ja hier nicht der Fall.

_________________
Na denn, dann. Bis dann, denn.
Seifriz Threadstarter
Hält's aus hier
Beiträge: 8

Win 7 - 64 Bit
Delphi 6 Prof., Delphi 2007 Win32
BeitragVerfasst: Mi 05.09.07 12:35 
Hi,

danke an alle für Eure Hilfe. Hab' wieder einiges über Threads dazugelernt...
Habe das 'Problem' im Griff. Das mit den Threads und den TStringList funktionierte (ob es guter Code ist :nixweiss: ).

Die Ursache lag an den doppelten Anführungszeichen um den dir-Befehl in der Consolen-Kommandozeile : :evil:
ausblenden Delphi-Quelltext
1:
2:
3:
...
if GetConsoleOutput('cmd /c psexec -u ' + PCName + '\' + Benutzer + ' -p ' + Passwort + ' -n 25 \\' + PCName + ' cmd /c "dir c:\"', Output, Errors) then
...


so klappt es
ausblenden Delphi-Quelltext
1:
2:
3:
...
if GetConsoleOutput('cmd /c psexec -u ' + PCName + '\' + Benutzer + ' -p ' + Passwort + ' -n 25 \\' + PCName + ' cmd /c dir c:\', Output, Errors) then
...


Grüße
F.S.