2016-05-28 5 views
1

Von the documentation of the ThreadPool.RegisterWaitForSingleObject Methode liest, ist es nicht klar, ob:Blockiert ThreadPool.RegisterWaitForSingleObject den aktuellen Thread oder einen Threadpool-Thread?

  1. Es blockiert den aktuellen Thread, während auf dem EventWaitHandle warten und dann die WaitOrTimerCallback auf einem Gewinde Thread-Pool in Auftrag gibt, oder

  2. Es beauftragt ein Thread-Pool-Thread, um auf den Wait-Handle zu warten und dann auf demselben Thread die WaitOrTimerCallback auszuführen, sobald der Wait-Handle signalisiert wurde.

  3. Es blockiert den aktuellen Thread und wenn das Wait-Handle signalisiert wird, ruft es den WaitOrTimerCallback auf dem aktuellen Thread. Aber das wäre die äquivalente Funktionalität von WaitHandle.WaitOne(). Außerdem würde es den Thread-Pool überhaupt nicht einbeziehen.

Welcher der drei ist das?

Antwort

6

Es ist keiner der oben genannten, 2) ist am nächsten. Die genauen Details sind ziemlich kompliziert, der Großteil des Codes ist in der CLR vergraben und hat sich in den .NET-Versionen geändert. Sie können einen Blick auf die aktuelle Version in the CoreCLR source werfen, ich werde die 10.000 Fuß Sicht geben.

Der Schlüssel ist, dass es nicht blockiert, die Arbeit wird von einem dedizierten nicht verwalteten Thread erledigt. Genannt der "Warte-Thread" im Quellcode, verwendet er die WaitForMultipleObjects() - winapi-Funktion, um auf alle registrierten Wartezeiten zu warten. Wenn es keine gibt (links), schläft es einfach. Der Thread wird entweder von einer QueueUserApc() geweckt, wenn sich die Warteliste ändert, so dass er mit einer aktualisierten Liste weiter warten kann.

Sobald eines der Wait-Objekte signalisiert wird, verwendet es ThreadPool.QueueUserWorkItem(), um das Callback-Delegat-Ziel in einem Threadpool-Thread aufzurufen. Wenn das Argument executeOnlyOnce wahr ist, wird das Wait-Handle aus der Liste entfernt. Und es nimmt schnell wieder mit WFMO zu warten. Der Thread endet nie.

Die executeOnlyOnce Argument ist wichtig übrigens, Heiterkeit ergibt sich, wenn Sie false übergeben und Sie ein ManualResetEvent verwenden.Die von der MRE-Methode Set() ausgelöste Thread-Explosion ist ein interessantes Artefakt, das beobachtet werden muss. Sie können den Warte-Thread im Debugger> Windows> Threads sehen, wenn Sie nicht verwaltetes Debugging aktivieren. Es hat jedoch keinen interessanten Namen.

+0

Vielen Dank. Es ist schon eine Weile her, seit ich C++ berührte und so ist mein Verständnis davon, was ich von der CLR-Quelle gelesen habe, begrenzt, aber ich versuche es. Wo finde ich die Definition/den Körper von 'QueueUserAPC'? Wie suche ich nach dem Körper einer Funktion? Ich habe das ** Find ** -Fenster im Browser verwendet, weil der Aufruf von 'QueueUserAPC' von keinem Empfänger/Objekt qualifiziert wurde, also habe ich in derselben Datei gesucht, konnte sie aber nicht finden. Außerdem sehe ich keinen Aufruf von 'WaitForMultipleObjects/Ex' im Quellcode für die' RegisterWaitForSingleObject'-Methode in dieser Datei. Wohin schaust du? –

+0

Schließlich, was ist APC? Ich schließe daraus, dass es eine Win32-Metapher für ein Workitem/eine Arbeit ist, die getan werden muss? –

+0

Es ist eine Betriebssystemfunktion, so dass es WFMO. Der Quellcode ist nur für Microsoft-Mitarbeiter sichtbar. Schauen Sie in MSDN nach, um zu sehen, was sie tun. APC ist asynchroner Prozeduraufruf, .NETs BeginInvoke() ist ungefähr ähnlich. Sehr grob. Der Begriff der alertfähigen Threads, die APCs ausführen können, ist in .NET nicht verfügbar, zu schwierig, um es richtig zu machen. –

1

Der folgende Testcode zeigt das Verhalten speziell genug, um Ihre Frage zu beantworten.

static bool bQuit = false; 
static string LastEntry; 

static void Main(string[] args) 
{ 
    EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset, "TestEvent"); 
    ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1)); 
    Console.WriteLine("TestEvent created."); 

    while (!bQuit) 
    { 
     Console.WriteLine("Press 1 to signal TestEvent.\nPress 2 to quit."); 
     switch (LastEntry = Console.ReadLine()) 
     { 
      case "1": 
       ewh.Set(); 
       break; 
      case "2": 
       bQuit = true; 
       break; 
     } 
    } 
    ewh.Dispose(); 
    Console.WriteLine("Press Enter to finish exiting."); 
    Console.ReadLine(); 
} 

static void Thread1(object data) 
{ 
    WaitHandle wh = EventWaitHandle.OpenExisting("TestEvent"); 
    RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(
     wh, new WaitOrTimerCallback(Thread2), null, Timeout.Infinite, false); 
    Console.WriteLine("Thread {0} registered another thread to run when TestEvent is signaled.", 
     Thread.CurrentThread.ManagedThreadId); 
    while(!bQuit) 
    { 
     Console.WriteLine("Thread {0} is running.", Thread.CurrentThread.ManagedThreadId); 
     Thread.Sleep(2000); 
    } 
    rwh.Unregister(wh); 
    Console.WriteLine("Thread {0} is exiting", Thread.CurrentThread.ManagedThreadId); 
} 

static void Thread2(object data, bool t) 
{ 
    Console.WriteLine("Thread {0} started", Thread.CurrentThread.ManagedThreadId); 
    while(!bQuit && (LastEntry != Thread.CurrentThread.ManagedThreadId.ToString())) 
    { 
     Console.WriteLine("Thread {0} is running. Enter {0} to end it", 
      Thread.CurrentThread.ManagedThreadId); 
     Thread.Sleep(2000); 
    } 
    Console.WriteLine("Thread {0} is exiting", Thread.CurrentThread.ManagedThreadId); 
} 

Die Ausgabe lautet:

TestEvent created. 
Thread 6 registered another thread to run when TestEvent is signaled. 
Thread 6 is running. 
Press 1 to signal TestEvent. 
Press 2 to quit. 
Thread 6 is running. 
Thread 6 is running. 
1 
Press 1 to signal TestEvent. 
Press 2 to quit. 
Thread 13 started 
Thread 13 is running. Enter 13 to end it 
Thread 6 is running. 
Thread 13 is running. Enter 13 to end it 
Thread 6 is running. 
Thread 13 is running. Enter 13 to end it 
1 
Press 1 to signal TestEvent. 
Press 2 to quit. 
Thread 14 started 
Thread 14 is running. Enter 14 to end it 
Thread 6 is running. 
Thread 13 is running. Enter 13 to end it 
Thread 14 is running. Enter 14 to end it 
Thread 6 is running. 
Thread 13 is running. Enter 13 to end it 
Thread 14 is running. Enter 14 to end it 
13 
Press 1 to signal TestEvent. 
Press 2 to quit. 
Thread 6 is running. 
Thread 13 is exiting 
Thread 14 is running. Enter 14 to end it 
Thread 6 is running. 
Thread 14 is running. Enter 14 to end it 
2 
Press Enter to finish exiting. 
Thread 6 is exiting 
Thread 14 is exiting 

So ist die Antwort auf Ihre Frage ist, glaube ich, # 2.

1

Ich habe den folgenden Test, um meine Frage zu beantworten. Die Antwort ist # 2.

using System; 
using System.Threading; 

namespace ThreadPoolRegisterWaitForSingleObject 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var allTasksWaitHandle = new AutoResetEvent(false); 

      Action action =() => 
      { 
       Console.WriteLine($"Long task running on {(Thread.CurrentThread.IsThreadPoolThread ? "thread pool" : "foreground")} thread Id: {Thread.CurrentThread.ManagedThreadId}"); 

       for (int i = 0; i < 1000000; i++) 
        for (int j = 0; j < 100000000; j++) ; 
      }; 

      //var result = action.BeginInvoke((state) => 
      //{ 
      // Console.WriteLine("Async call back says long thing done."); 
      //}, null); 

      var result = action.BeginInvoke(null, null); 

      Console.WriteLine("Main thread not blocked."); 

      var registerWaitHandle = ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, (s, b) => 
      { 
       Console.WriteLine("Main long task finished."); 
       Console.WriteLine($"WaitOrTimerCallback running on {(Thread.CurrentThread.IsThreadPoolThread ? "thread pool" : "foreground")} thread Id: {Thread.CurrentThread.ManagedThreadId}"); 

       allTasksWaitHandle.Set(); 
      }, null, 5000, true); 

      allTasksWaitHandle.WaitOne(); 

      Console.WriteLine("All threads done."); 
      Console.WriteLine("Press any key to exit..."); 
      Console.ReadKey(); 
     } 
    } 
} 

Auch dieser besondere Satz von the documentation of the ThreadPool class legt nahe, dass der Rückruf auf einem Thread-Pool-Thread aufgerufen wird.

Wenn Sie registrierte Wait-Handles verwenden, überwacht ein System-Thread den Status der Wait-Handles. Wenn eine Warteoperation abgeschlossen ist, führt ein Worker-Thread aus dem Thread-Pool die entsprechende Callback-Funktion aus.