Klasa ManualResetEventSlim a ManualResetEvent

O klasie ManualResetEvent pisałem  na blogu już wielokrotnie. Prosty przykład:

internal static class Sample
{
   private static readonly ManualResetEvent _manualEvent=new ManualResetEvent(false);

   public static void Main()
   {
       Task.Factory.StartNew(Run);
       _manualEvent.WaitOne();
       Console.WriteLine("Exiting...");
   }
   private static void Run()
   {
       Thread.Sleep(1000);
       Console.WriteLine("Run");
       _manualEvent.Set();
   }
}

Zastosowanie ManualResetEvent oraz AutoResetEvent jest szerokie i obejmuje m.in. testy jednostkowe czy synchronizację wątków. W dzisiejszym wpisie jednak nie o tym – jeśli powyższa konstrukcja jest nieznana lub niezrozumiała w pełni to warto poszukać o tym informacji choćby na tym blogu lub np. na MSDN. Dzisiaj chciałbym pokazać, że istnieje pewna odmiana powyższej klasy nazwana ManualResetEventSlim.

Dlaczego ją stworzono? ManualResetEvent jest bardzo skomplikowaną klasą, która pochłania dużo zasobów. Nie zawsze istnieje potrzeba korzystania z niej i dlatego wprowadzono odchudzoną wersję ManualResetEventSlim. Czy jest zatem sens używania ManualResetEvent, który jest tak bardzo rozpowszechniony? Jaka jest różnica pomiędzy powyższymi klasami?

Skrócona wersja to:

  1. Używaj ManualResetEvent gdy dana operacja będzie trwała długo i co za tym idzie, WaitOne będzie musiał blokować wątek na dłuższy czas.
  2. Używaj ManualResetEventSlim dla krótkich operacji, gdzie blokada nie będzie trwała długo.

ManualResetEvent jest implementacją typowo po stronie kernal’a. A co tym idzie, komunikacja .NET-Kernel musi mieć miejsce, co jest olbrzymim obciążeniem dla CPU i pamięci. Zawsze powinnyśmy preferować rozwiązania czysto .NET, gdzie nie ma komunikacji między światem zarządzanym a niezarządzanym. Oczywiście rozwiązania kernelowe mają przewagę dla długich operacji. Potrafią one bowiem uśpić wątek i zaplanować jakąś inną pracę. Rozwiązania synchronizacyjne bazujące na czystym .NET to tylko zwykły spinning (patrz poprzednie posty jeśli jest to niezrozumiałe). Spinning to nic innego jak zwykła pętla a zatem marnuje ona CPU. Jest to doskonałe dla krótkich operacji bo wtedy nie trzeba komunikować się z kernel, usypiać wątku, planować następnego i w końcu context switching. Dla długich operacji spinning jest  jednak marnowaniem zasobów– wątek nic nie robi tylko wykonuje pustą pętle.

ManualResetEventSlim to rozwiązanie hybrydowe – na początku będzie korzystał z spinningu, a dopiero potem wywoła Monitor.Enter, który z kolei na końcu spowoduje uśpienie wątku i komunikację z kernel. Jeśli mamy świadomość, że operacja potrwa długo wtedy nie ma sensu blokować wątku przez spinning i od razu lepiej skorzystać z ManualResetEvent. W przeciwnym wypadku, ManualResetEventSlim i jego spinning może przynieść bardzo pozytywne skutki. Proszę zauważyć, że ManualResetEventSlim również uśpi wątek po jakimś czasie więc nawet dla długich czasów oczekiwania, klasa nie przyniesie ekstremalnie złych efektów (spinning przez długi czas jest bardzo złym rozwiązaniem). Nie oznacza to oczywiście, że ZAWSZE preferować należy ManualResetEventSlim – jeśli wiemy, że operacja trochę potrwa wtedy nie ma najmniejszego sensu wykonywać pustej pętli.

Sposób korzystania jest praktycznie identyczny:

internal static class Sample
{
   private static readonly ManualResetEventSlim _manualEvent = new ManualResetEventSlim(false);

   public static void Main()
   {
       Task.Factory.StartNew(Run);
       _manualEvent.Wait();
       Console.WriteLine("Exiting...");
   }
   private static void Run()
   {
       Thread.Sleep(1000);
       Console.WriteLine("Run");
       _manualEvent.Set();
   }
}

Leave a Reply

Your email address will not be published.