簡體   English   中英

.NET中的ManualResetEvent和AutoResetEvent有什么區別?

[英]What is the difference between ManualResetEvent and AutoResetEvent in .NET?

我已閱讀有關此文檔,我想我明白了。 當代碼通過event.WaitOne()AutoResetEvent重置,但ManualResetEvent則不會。

它是否正確?

是。 這就像收費站和門之間的區別。 ManualResetEvent是門,需要手動關閉(重置)。 AutoResetEvent是一個收費站,允許一輛汽車經過並在下一輛汽車通過之前自動關閉。

試想一下, AutoResetEvent WaitOne()Reset()作為單個原子操作執行。

簡短的回答是肯定的。 最重要的區別是AutoResetEvent只允許一個等待的線程繼續。 另一方面,ManualResetEvent將繼續允許線程(幾個同時均勻)繼續,直到你告訴它停止(重置它)。

取自Joseph#Albahari的C#3.0 Nutshell書

C#中的線程 - 免費電子書

ManualResetEvent是AutoResetEvent的變體。 它的不同之處在於它在WaitOne調用中通過線程后不會自動復位,因此像gate:call Set這樣的函數會打開門,允許任何數量的線程在門口通過WaitOne; 調用Reset關閉門,可能導致服務器隊列累積,直到下一次打開。

可以使用布爾“gateOpen”字段(使用volatile關鍵字聲明)與“spin-sleeping”結合來模擬此功能 ​​- 重復檢查標志,然后在短時間內休眠。

ManualResetEvents有時用於表示特定操作已完成,或者線程已完成初始化並准備好執行工作。

我創建了簡單的示例來闡明對ManualResetEventAutoResetEvent理解。

AutoResetEvent :假設您有3個工作線程。 如果任何這些線程將調用WaitOne()所有其他2個線程將停止執行並等待信號。 我假設他們正在使用WaitOne() 它像是; 如果我不工作,沒人會工作。 在第一個例子中,你可以看到

autoReset.Set();
Thread.Sleep(1000);
autoReset.Set();

當你調用Set()所有線程都會工作並等待信號。 1秒鍾后,我發送第二個信號,然后執行並等待( WaitOne() )。 想想這些家伙是足球隊員,如果有一名球員說我會等到經理給我打電話,其他人會等到經理告訴他們繼續( Set()

public class AutoResetEventSample
{
    private AutoResetEvent autoReset = new AutoResetEvent(false);

    public void RunAll()
    {
        new Thread(Worker1).Start();
        new Thread(Worker2).Start();
        new Thread(Worker3).Start();
        autoReset.Set();
        Thread.Sleep(1000);
        autoReset.Set();
        Console.WriteLine("Main thread reached to end.");
    }

    public void Worker1()
    {
        Console.WriteLine("Entered in worker 1");
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker1 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
    public void Worker2()
    {
        Console.WriteLine("Entered in worker 2");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker2 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
    public void Worker3()
    {
        Console.WriteLine("Entered in worker 3");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker3 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
}

在這個例子中,你可以清楚地看到,當你第一次點擊Set()它會讓所有線程都進入,然后在1秒后它會發出所有線程等待的信號! 一旦你再次設置它們而不管它們是否在內部調用WaitOne() ,它們將繼續運行,因為你必須手動調用Reset()來停止它們。

manualReset.Set();
Thread.Sleep(1000);
manualReset.Reset();
Console.WriteLine("Press to release all threads.");
Console.ReadLine();
manualReset.Set();

它更多的是關於裁判/球員的關系,無論任何一名球員受傷,等待比賽的人都會繼續工作。 如果裁判說等待( Reset() ),則所有玩家將等到下一個信號。

public class ManualResetEventSample
{
    private ManualResetEvent manualReset = new ManualResetEvent(false);

    public void RunAll()
    {
        new Thread(Worker1).Start();
        new Thread(Worker2).Start();
        new Thread(Worker3).Start();
        manualReset.Set();
        Thread.Sleep(1000);
        manualReset.Reset();
        Console.WriteLine("Press to release all threads.");
        Console.ReadLine();
        manualReset.Set();
        Console.WriteLine("Main thread reached to end.");
    }

    public void Worker1()
    {
        Console.WriteLine("Entered in worker 1");
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker1 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
    public void Worker2()
    {
        Console.WriteLine("Entered in worker 2");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker2 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
    public void Worker3()
    {
        Console.WriteLine("Entered in worker 3");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker3 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
}

autoResetEvent.WaitOne()

類似於

try
{
   manualResetEvent.WaitOne();
}
finally
{
   manualResetEvent.Reset();
}

作為原子操作

好的,通常在同一個帖子中添加2個答案不是一個好習慣,但我不想編輯/刪除我以前的答案,因為它可以幫助另一種方式。

現在,我在下面創建了更加全面且易於理解的“運行 - 學習”控制台應用程序片段。

只需在兩個不同的控制台上運行示例,並觀察行為。 你會更清楚地了解幕后發生的事情。

手動重置事件

using System;
using System.Threading;

namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
    public class ManualResetEventSample
    {
        private readonly ManualResetEvent _manualReset = new ManualResetEvent(false);

        public void RunAll()
        {
            new Thread(Worker1).Start();
            new Thread(Worker2).Start();
            new Thread(Worker3).Start();
            Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
            Thread.Sleep(15000);
            Console.WriteLine("1- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("2- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("3- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("4- Main will call ManualResetEvent.Reset() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("It ran one more time. Why? Even Reset Sets the state of the event to nonsignaled (false), causing threads to block, this will initial the state, and threads will run again until they WaitOne().");
            Thread.Sleep(10000);
            Console.WriteLine();
            Console.WriteLine("This will go so on. Everytime you call Set(), ManualResetEvent will let ALL threads to run. So if you want synchronization between them, consider using AutoReset event, or simply user TPL (Task Parallel Library).");
            Thread.Sleep(5000);
            Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

        }

        public void Worker1()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker1 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker2()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker2 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker3()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker3 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }

}

手動重置事件輸出

自動重置事件

using System;
using System.Threading;

namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
    public class AutoResetEventSample
    {
        private readonly AutoResetEvent _autoReset = new AutoResetEvent(false);

        public void RunAll()
        {
            new Thread(Worker1).Start();
            new Thread(Worker2).Start();
            new Thread(Worker3).Start();
            Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
            Thread.Sleep(15000);
            Console.WriteLine("1- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("2- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("3- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("4- Main will call AutoResetEvent.Reset() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("Nothing happened. Why? Becasuse Reset Sets the state of the event to nonsignaled, causing threads to block. Since they are already blocked, it will not affect anything.");
            Thread.Sleep(10000);
            Console.WriteLine("This will go so on. Everytime you call Set(), AutoResetEvent will let another thread to run. It will make it automatically, so you do not need to worry about thread running order, unless you want it manually!");
            Thread.Sleep(5000);
            Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

        }

        public void Worker1()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker1 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker2()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker2 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker3()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker3 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }

}

自動重置事件輸出

恩,那就對了。

您可以通過使用這兩個來獲得一個想法。

如果你需要告訴你已經完成了一些工作而其他(線程)等待這個現在可以繼續,你應該使用ManualResetEvent。

如果您需要對任何資源進行互斥訪問,則應使用AutoResetEvent。

是。 這絕對是正確的。

您可以將ManualResetEvent視為指示狀態的方式。 某些東西打開(設置)或關閉(重置)。 有一段時間的事件。 等待該狀態發生的任何線程都可以繼續。

AutoResetEvent與信號更具可比性。 一次性表明事情已經發生。 沒有任何持續時間的事件。 通常但不一定發生的“某事”很小並且需要由單個線程處理 - 因此在單個線程消耗了事件之后自動重置。

AutoResetEvent在內存中維護一個布爾變量。 如果布爾變量為false,則它會阻塞線程,如果布爾變量為true,則取消阻塞線程。

當我們實例化一個AutoResetEvent對象時,我們在構造函數中傳遞boolean value的默認值。 下面是實例化AutoResetEvent對象的語法。

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

WaitOne方法

此方法阻塞當前線程並等待其他線程的信號。 WaitOne方法將當前線程置於Sleep線程狀態。 如果WaitOne方法收到信號則返回true,否則返回false。

autoResetEvent.WaitOne();

WaitOne方法的第二次重載等待指定的秒數。 如果它沒有得到任何信號線程繼續其工作。

static void ThreadMethod()
{
    while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2)))
    {
        Console.WriteLine("Continue");
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }

    Console.WriteLine("Thread got signal");
}

我們通過傳遞2秒作為參數來調用WaitOne方法。 在while循環中,它等待信號2秒然后繼續工作。 當線程得到信號時,WaitOne返回true並退出循環並打印“Thread got signal”。

設定方法

AutoResetEvent Set方法將信號發送到等待線程以繼續其工作。 下面是調用Set方法的語法。

autoResetEvent.Set();

ManualResetEvent在內存中維護一個布爾變量。 當布爾變量為false時,它會阻塞所有線程,當布爾變量為true時,它會解除阻塞所有線程。

當我們實例化ManualResetEvent時,我們使用默認的布爾值初始化它。

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

在上面的代碼中,我們使用false值初始化ManualResetEvent,這意味着調用WaitOne方法的所有線程都將阻塞,直到某個線程調用Set()方法。

如果我們使用true值初始化ManualResetEvent,則調用WaitOne方法的所有線程都不會阻塞並自由進一步繼續。

WaitOne方法

此方法阻塞當前線程並等待其他線程的信號。 如果接收到信號則返回true,否則返回false。

下面是調用WaitOne方法的語法。

manualResetEvent.WaitOne();

在WaitOne方法的第二個重載中,我們可以指定當前線程等待信號的時間間隔。 如果在內部時間內,它沒有收到信號,它返回false並進入下一行方法。

下面是使用時間間隔調用WaitOne方法的語法。

bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));

我們在WaitOne方法中指定了5秒。 如果manualResetEvent對象在5秒內沒有收到信號,則將isSignalled變量設置為false。

設置方法

此方法用於將信號發送到所有等待的線程。 Set()方法將ManualResetEvent對象的boolean變量設置為true。 所有等待的線程都被解鎖並繼續進行。

下面是調用Set()方法的語法。

manualResetEvent.Set();

重置方法

一旦我們在ManualResetEvent對象上調用Set()方法,它的布爾值保持為true。 要重置值,我們可以使用Reset()方法。 Reset方法將布爾值更改為false。

下面是調用Reset方法的語法。

manualResetEvent.Reset();

如果我們想多次向線程發送信號,我們必須在調用Set方法后立即調用Reset方法。

如果您想了解AutoResetEvent和ManualResetEvent,您需要了解不是線程而是中斷!

.NET希望盡可能地喚起低級編程。

中斷是低級編程中使用的東西,等於從低變高(或反之)的信號。 發生這種情況時,程序會中斷其正常執行並將執行指針移動到處理此事件的函數。

當中斷發生時要做的第一件事是重置其狀態,因為硬件以這種方式工作:

  1. 引腳連接到信號,硬件監聽它的變化(信號只能有兩種狀態)。
  2. 如果信號發生變化意味着發生了某些事情並且硬件將一個記憶變量發送到發生的狀態(即使信號再次發生變化,它仍然是這樣)。
  3. 程序注意到變量狀態並將執行移動到處理函數。
  4. 這里要做的第一件事,就是能夠再次監聽這個中斷,就是這個內存變量重置為未發生的狀態。

這是ManualResetEvent和AutoResetEvent之間的區別。
如果發生了ManualResetEvent並且我沒有重置它,下次它發生時我將無法收聽它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM