Entwickler-Ecke

Windows API - SuspendThread - Thread Ausführung unterbrechen


FrEEzE2046 - Mo 19.10.09 12:46
Titel: SuspendThread - Thread Ausführung unterbrechen
Hallo,

ich brauche drigend euere Hilfe ;-). Ich habe einen Thread der mehrfach gestartet werden muss. Da das eigentliche Programme zu viel Komplexität hat, habe ich das Problem auf ein ganz einfaches Beispiel heruntergebrochen:


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:
program RTT;
{$APPTYPE CONSOLE}
uses
  Windows,
  sysutils;

type
  TParam = record
    PHandle : ^THandle;
    PInt    : ^Integer;
  end;

var
  Int        : Integer = 0;
  hThread    : THandle;
  ExitEvent  : THandle;
  dwThreadID : DWORD;
  Param       : TParam;
  msg         : tagMSG;
  dwStatus   : DWORD;
  bCanceled  : Boolean = false;


function ThreadFunc(lpParams: Pointer): Integer; stdcall;
var
  PParam : ^TParam absolute lpParams;
begin
  while not bCanceled do
  begin
    Inc(PParam^.PInt^);
    SetEvent(ExitEvent);
    SuspendThread(PParam^.PHandle^);
    writeln('test');
  end;

  writeln('Thread will be terminated');
  ExitThread(0);
end;


begin

  ExitEvent := CreateEvent(nil, true, false, nil);

  hThread := CreateThread(
    nil,
    0,
    @ThreadFunc,
    @Param,
    CREATE_SUSPENDED,
    dwThreadID
  );

  Param.PHandle := @hThread;
  Param.PInt     := @Int;

  while( Int <> 1000 ) do
  begin
    ResumeThread(hThread);

    while(true) do
    begin
      while PeekMessage(msg, 000, PM_REMOVE) do
        DispatchMessage(msg);

      dwStatus := MsgWaitForMultipleObjects(1, ExitEvent, false, INFINITE, QS_ALLINPUT);

      if( dwStatus = (WAIT_OBJECT_0 + 1) ) then
        continue
      else
        break;
    end;

    writeln(Int);
  end;

  bCanceled := true;
  ResumeThread(hThread);

  readln;
end.



Leider funktioniert das nicht wirklich. Mal wird eine Zahl doppelt ausgegeben, mal kommt mehrfach hintereinander "test" ... wie kann man das richtig lösen? SuspendThread ist ja mehr für Debugger.


Lossy eX - Mo 19.10.09 15:42

Du gehst davon aus, dass SuspendThread deinen Thread sofort anhält. Das würde ich nicht tun. SuspendThread oder ResultThread sorgen nur dafür, dass der TaskSheduler von Windows weiß, dass der Thread gestoppt oder gestartet werden soll. Deine Methode läuft aber noch. Erst, wenn der TaskSheduler diese Methode unterbricht (oder sie sich selbst) würde er den Thread schlafen legen. Entsprechend könnte es also durchaus passieren, dass dein Thread mehrere Durchläufe schafft bevor der TaskSheduler ihn schlafen legt. Das würde das wiederholende "Test" erklären.

^THandle, ^Integer und ^TParam solltest du jeweils als eigene Typen deklarieren. Dann kannst du deine Parameter auch einfach Casten und musst nicht mit so etwas wie absolut arbeiten. Folgendes und der Drops ist gelutscht.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
type 
  PParam = ^TParam;

function ThreadFunc(lpParams: Pointer): Integer; stdcall;
begin
  Inc(PParam(lpParam)^.PInt^);


Du verwendest ein Event was manuell zurück gesetzt werden muss (zweiter Parameter). Dieser Status wird also nicht durch ein WaitFor* zurück gesetzt. Entsprechend wird es so sein, dass dein Thread dieses Event ein mal setzt und es immer als gesetzt angezeigt wird. Das erklärt auch warum du doppelte Zahlen siehst. Vermutung: Wenn dein Thread mehr als nur ein Inc machen würde, dann dürfte sich das noch verstärken.

Zu deinen Threads. Es lässt sich anhand deines Beispiels nur schwer sagen was die beste Architektur ist. Wenn die Aufgaben groß genug sind könntest du auch schlicht einen Thread starten und warten bis sich dieser Beendet. Anschließend startest du wieder einen neuen Thread. Wobei das auch nur Sinn macht, wenn du mehr als einen Thread startest.

Ansonsten würde ich eventuell mit mehr als einen Event arbeiten. Also 2 Events pro Thread. Ein Event gibt an, dass der Thread arbeit hat. Darauf muss der Thread warten. Wenn es, von deiner Hauptschleife, gesetzt wurde, dann erledigt der Thread seine Arbeit und setzt ein zweites Event was signalisiert, dass seine Arbeit erledigt ist. Deine Hauptanwendung würde wiederrum auf das Zweite warten. Wärend deine Hauptanwendung also feststellt, dass ein "arbeit erledigt" Event gesetzt wurde muss es sofort neue Arbeit an den Thread reichen und das "Arbeit verfügbar" Event setzen.

Muss ich erwähnen, dass es das Beste wäre, wenn du alle deine threadspezifischen Daten in einer Klasse kappselst und diese Klasse sich dann entsprechend um den Thread kümmert?


FrEEzE2046 - Di 20.10.09 11:47

Hallo,

erst einmal danke für deine Antwort. Ich habe das Problem jetzt in der Tat mit mehreren Events gelöst, stehe jetzt aber vor dem nächsten. Es ist so, dass eine Funktion, die innerhalb des Threads läuft sich rekursiv aufruft. Das führt leider zu einem Stack overflow.

Ich habe versucht, der Funktion CreateThread STACK_SIZE_PARAM_IS_A_RESERVATION als CreationFlag zu übergeben und bei dwStackSize den Wert 8 KiloByte gewählt (also das doppelte vom default Wert). Leider zeigt das überhaupt keine Wirkung.

Die Funktion SetThreadStackGuarantee kann ich leider nicht benutzen, da es auch unter Windows XP 32 Bit laufen muss.

Hast du eine Idee?


Lossy eX - Di 20.10.09 14:08

Ohne Code kann dir dazu niemand etwas sagen.

Aber Rekursionen sind normal so, dass eine Methode sich selbst aufruft und nach getaner Arbeit sich auch wieder verlässt. Also, dass du wieder in der Hauptmethode landest. Ansonsten müsste man überprüfen was du für Parameter an die Methoden übergibst und was für lokale Variablen existieren bzw auch wie oft du die Methode aufrufst. Wenn da was großes dabei ist, dann sind 8KB recht schnell weg. Ich meine Delphi benutzt für so etwas normal 1MB. Wenn du das flag weg lasst, dann nimmt er die Stackgröße der Anwendung. Was spricht denn da dagegen?


FrEEzE2046 - Mi 21.10.09 13:16

user profile iconLossy eX hat folgendes geschrieben Zum zitierten Posting springen:
Ohne Code kann dir dazu niemand etwas sagen.

Aber Rekursionen sind normal so, dass eine Methode sich selbst aufruft und nach getaner Arbeit sich auch wieder verlässt. Also, dass du wieder in der Hauptmethode landest. Ansonsten müsste man überprüfen was du für Parameter an die Methoden übergibst und was für lokale Variablen existieren bzw auch wie oft du die Methode aufrufst. Wenn da was großes dabei ist, dann sind 8KB recht schnell weg. Ich meine Delphi benutzt für so etwas normal 1MB. Wenn du das flag weg lasst, dann nimmt er die Stackgröße der Anwendung. Was spricht denn da dagegen?



Hallo,

also es ist folgendermaßen:

Ich habe natürlich zu beginn die Default Stackgröße der Anwendung benutzt (implizit, also einfach ohne Definition). Da löste mein Programm immer eine "benutzerdefinierte Exception" aus. Da mir dies absolut keinen Hinweis darauf gibt, wo genau der Fehler aufgetreten ist, habe ich innerhalb des Threads einen try ... except Block eingefügt. Dieser brachte dann die Message "Stacküberlauf" zu Tage.
Daher habe ich eine Möglichkeit gesucht, die Stackgröße zu erhöhen. Vor allem da ich ohnehin auf einen Stacküberlauf getippt hatte (aufgrund der Rekursion). Natürlich baut sich die Rekursion wieder von selber ab, allerdings wird die Funktion auch sehr häufig aufgerufen.
Leider kann ich dir den Source nicht zur Verfügung stellen, die Parameter sind aber lediglich 2 Int64.

Wenn die default Größe bei 1 MB liegen würde, könnte ich die Funktion immerhin 2^16 = 65536 mal aufrufen. In meinen Projektoptionen ist standardmäßig die Rede von:

Minimale Stackgröße = $00004000 = 16 kB = 1024 Aufrufe (hier wird es schon kritisch).
Maximale Stackgröße = $00100000 = 1024 kB = 1 MB

Ich weiß jetzt natürlich nicht, inwiefern und wann er den Stack "vergrößert". Erkennt er über die Notwendigkeit? Soll heißen: Erkennt er, dass hier etwas rekursiv läuft und er daher mehr Stack braucht?

Man darf nicht vergessen, dass die Rechnung, wie ich sie oben gemacht habe, im Prinzip nicht ganz richtig ist. Auf dem Stack werden auch Dinge wie die Rücksprungadresse, sowie Lokalevariablen abgelegt, was natürlich die Anzahl der möglichen Aufrufe verringert. Lokale Variablen sind nochmal 2 Int64 vorhanden und da das Ganze in einer Klasse abläuft, bin ich mir nicht sicher, wie viel er da noch benötigt.

Da ich nicht an der programmeigenen Einstellung ansetzen möchte, würde ich gerne den Stack nur für den Thread vergrößern.


Lossy eX - Mi 21.10.09 14:05

So wirklich hilft mir das jetzt aber nicht.

Bei Klassen wird noch mal ein unsichtbarer Pointer übergeben. Ansonsten wird der Stack nur durch lokale Variablen und Funktionsparameter beeinflusst. Plus interne Datenstrukturen (Rücksprungadressen). Aber was da an internen genau drinne steckt weiß ich nicht. Try Finally, StackFrames dürften da gut zu Buche schlagen.

Der Stack sollte sich normal selbst erweitern. Müsste sogar Windows machen. Muss aber gestehen, dass ich selbst bisher noch nie so etwas benötigt hatte. Also Rekursionen mit 1000 Aufrufen. Vielleicht solltest du deine rekursive Struktur da auch etwas flacher halten. Also mit einer Liste oder so. Aber ich weiß natürlich nicht was du da machst. Also von daher kann ich so auch nicht wirklich was sinnvolles dazu sagen.