Autor Beitrag
Ares
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 128



BeitragVerfasst: Mi 12.01.11 12:11 
Hallo!

Ich habe mit Threads noch nicht sonderlich viel Erfahrung und bin mir daher nicht sicher, ob ich folgende Aufgabe richtig angehe:

In einem Programm soll eine zeitaufwändige Aufgabe (Mails abrufen, Datei speichern, was auch immer...) in einen separaten Thread ausgelagert werden um das Hauptprogramm nicht zu blockieren. Die Nebenaufgabe soll zudem pausiert und später wieder fortgesetzt werden können.

ausblenden C#-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:
public class Hauptprogramm {
   private Thread nebenAufgabe;

   public void Hauptprogramm() {
      ...
      nebenAufgabe = new Thread(new ThreadStart(ErledigeNebenAufgabe));
      nebenAufgabe.Start();

      arbeiteWeiter();
   }

   private void ErledigeNebenAufgabe() {
      ...
   }

   // Eventhandler für Buttons mit denen der Thread kontrolliert werden soll
   private void StopButtonClick(object sender, EventArgs e) {
      if (nebenAufgabe.ThreadState == ThreadState.Running)
         nebenAufgabe.Suspend();
   }

   private void ResumeButtonClick(object sender, EventArgs e) {
      if (nebenAufgabe.ThreadState == ThreadState.Stopped)
         nebenAufgabe.Resume();
   }
}


Für das Anhalten und Fortsetzten des Nebenthreads verwende ich also nebenAufgabe.Suspend() und .Resume(). Hierbei zeigt mir das Compiler allerdings die Warnung, dass dies veraltet ist und besser ein Monitor verwendet werden soll. MSDN sagt dazu, dass bei Suspend das Risiko von Deadlocks sehr groß ist. Wird der Thread unterbrochen während er sich gerade in einem vom ihm gesperrten Abschnitt befindet oder während er einen Konstruktor ausführt, kann kein anderer Thread in diesen Abschnitt bzw. diesen Konstruktor ausführen --> Deadlock...

Suspend() und .Resume() scheinen für diesen Zweck also nicht gut geeignet zu sein. Wie löst man diese Aufgabe "Pausieren einer Aufgabe und später wieder fortsetzten" aber dann korrekt?

Der Verweis auf die Verwendung eines Monitor bringt mich nicht weiter. Soweit ich das verstehe kann ich hiermit "nur" den Zugriff auf kritische Abschnitte regeln. Aber wie halte ich einen Thread damit sicher an ohne ein Deadlock zu riskieren?

Es müsste eine Möglichkeit geben, mit der das Hauptprogramm dem Nebenthread signalisieren kann "Halte bitte an". Dann müsst der Nebenthread noch weiter arbeiten können, bis er nichts mehr blockiert und erst dann anhalten.

Wie löst man dies korrekt?

Besten Dank
Ares
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mi 12.01.11 13:21 
Zitat:
Wie löst man dies korrekt?


Mit einem besagten Monitor oder einer der WaitHandle Klassen z.B dem ManualResetEvent.

Stellt sich aber die Frage ob anhalten nötig ist. Oft empfinde ich beenden und einen neuen Thread verwenden/starten einfacher. Entweder hat der Thread keinen eigenen Zustand und kann einfach beendet und neu gestartet werden(reiner Überwachungthread) oder der State ist einfach außerhalb des Threads zu halten(z.B. abarbeiten einer Liste von gleichartigen Dingen. State ist einfach die zuletzt bearbeitete Id der Aufgabe) und kann in einem anderen folgenden Thread weiterverwendet werden.
Ares Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 128



BeitragVerfasst: Mi 12.01.11 16:36 
EventWaitHandle und ManualResetEvent waren die Stichworte die mir gefehlt haben, vielen Dank.

Beim Konzept der EventWaitHandle habe ich aber noch Verständnisprobleme. Meine Lösung sieht nun ca. so aus:

ausblenden volle Höhe C#-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:
public class Hauptprogramm {
   private Thread nebenAufgabe;
   private SyncEvents syncEvents = new SyncEvents();

   public void Hauptprogramm() {
      ...
      nebenAufgabe = new Thread(new ThreadStart(ErledigeNebenAufgabe));
      nebenAufgabe.Start();

      arbeiteWeiter();
   }

   private void ErledigeNebenAufgabe() {
      while (Irgenwas) {
         // Solange Pause signalisiert ist abwarten
         while (syncEvents.PauseThreadSignal.WaitOne(0true)
            Thread.Sleep(0);

         ErledigeKritischenSchritt();
      }
   }

   // Eventhandler für Buttons mit denen der Thread kontrolliert werden soll
   private void StopButtonClick(object sender, EventArgs e) {
      if (nebenAufgabe.ThreadState == ThreadState.Running)
         syncEvents.PauseThreadSignal.Set();
   }

   private void ResumeButtonClick(object sender, EventArgs e) {
      if (nebenAufgabe.ThreadState == ThreadState.Running)
         syncEvents.PauseThreadSignal.Reset();
   }
}


public class SyncEvents {
   private EventWaitHandle pauseThreadSignal;

   public SyncEvents() {
      pauseThreadSignal = new ManualResetEvent(false);
   }

   public EventWaitHandle PauseThreadSignal {
      get { return pauseThreadSignal; }
   }
}


ErledigeNebenAufgabe() prüft nun also vor jedem kritischen Schritt ob signalisiert ist, dass pausiert werden soll. Ist dies der Fall wartet der Thread solange bis das Signal wieder aufgehoben wird. Danach wird der kritische Schritt komplett ausgeführt bevor die nächste Überprüfung durchgeführt wird.


Ich weiß allerdings nicht, ob ich das Konzept hinter den EventWaitHandle durchschaut habe. Mich verwirrt der Name "EventWaitHandle". Es handelt sich doch eigentlich nicht um eine Ereignis auf das gewartet wird, sondern doch um ein Signal, dass gesetzt oder nicht gesetzt ist. Oder verstehe ich das falsch?

Man könnte das gleiche Ergebnis doch auch einfach dadurch erzielen, dass man keine EventWaitHandle sondern einfache Bool-Flags verwendet: Der Hauptthread setzt bool pauseThread = true und der Nebenthread prüft pauseThread = true und wartet entsprechend.

Der Vorteil den ich bei EventWaitHandle sehe ist, dass das Setzen und Lesen des Signals schon threadsicher ist und das Signal z.B. auch automatisch zurückgesetzt werden kann nachdem es gelesen wurde. Aber vom Prinzip ist es ein Flag (mit Zusatzfunktionen).

Sehe ich das richtig? Oder habe ich an dem Konzept etwas missverstanden?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mi 12.01.11 16:58 
Einen Bool müsstest du in dem nebenläufigen Thread ständig abfragen(im Prnzip ein Busy Waiting). Ein ManualResetEvent ~hängt~ im Aufruf von WaitOne(der Thread verbraucht für nichts mehr Zeit) bis jemand anderes Set aufruft. Die While Schleife in deinem Code ist also eigentlich unnötig wenn der an WaitOne übergebenen Timeout etwas länger oder unendlich(-1) wäre. 0, also nicht auf Set warten sondern sofort weitermachen, ist glaube ich Unsinn.