C#中AutoResetEvent控制線程用法小結(jié)
本文主要來自一道面試題,由于之前對AutoResetEvent的概念比較模糊(即使已經(jīng)使用過了)。面試題題目很簡潔:兩個線程交替打印0~100的奇偶數(shù)。你可以先動手試試,我主要是嘗試在一個方法里面完成這個任務(wù)。
注: Suspend,Resume來控制線程已經(jīng)在.net framework2.0被淘汰了,原因就是掛起之后,但因為異常而沒有及時恢復(fù),如果占用資源會導(dǎo)致死鎖。
- AutoResetEvent對象用來進(jìn)行線程同步操作,AutoResetEvent類繼承waitHandle類。waitOne()方法就繼承來自waitHandle類。
- AutoResetEvent對象有終止和非終止兩種狀態(tài),終止?fàn)顟B(tài)是線程繼續(xù)執(zhí)行,非終止?fàn)顟B(tài)使線程阻塞,可以調(diào)用set和reset方法使對象進(jìn)入終止和非終止?fàn)顟B(tài)。-》可以簡單理解如果AutoResetEvent對象是終止?fàn)顟B(tài),就像不管別人了,任你撒野去(waitOne()得到的都是撒野信號)
- AutoResetEvent顧名思義,其對象在調(diào)用一次set之后會自動調(diào)用一次reset,進(jìn)入非終止?fàn)顟B(tài)使調(diào)用了等待方法的線程進(jìn)入阻塞狀態(tài)。-》可以簡單理解如果AutoResetEvent對象是非終止?fàn)顟B(tài),就開始管理起別人來了,此時waitOne()得到的信號都是呆在原地不動信號。
- waitHandle對象的waitone可以使當(dāng)前線程進(jìn)入阻塞狀態(tài),等待一個信號。直到當(dāng)前 waitHandle對象收到信號,才會繼續(xù)執(zhí)行。
- set可以發(fā)送一個信號,允許一個調(diào)用waitone而等待線程繼續(xù)執(zhí)行。 ManulResetEvent的set方法可以允許多個。但是要手動關(guān)閉,即調(diào)用reset();
- reset可以使因為調(diào)用waitone() 而等待線程都進(jìn)入阻塞狀態(tài)。
AutoResetEvent主要方法及實踐
- AutoResetEvent(bool initialState):構(gòu)造函數(shù),用一個指示是否將初始狀態(tài)設(shè)置為終止的布爾值初始化該類的新實例。 false:無信號,子線程的WaitOne方法不會被自動調(diào)用 true:有信號,子線程的WaitOne方法會被自動調(diào)用
- Reset ():將事件狀態(tài)設(shè)置為非終止?fàn)顟B(tài),導(dǎo)致線程阻止;如果該操作成功,則返回true;否則,返回false。
- Set ():將事件狀態(tài)設(shè)置為終止?fàn)顟B(tài),允許一個或多個等待線程繼續(xù);如果該操作成功,則返回true;否則,返回false。
- WaitOne(): 阻止當(dāng)前線程,直到收到信號。
- WaitOne(TimeSpan, Boolean) :阻止當(dāng)前線程,直到當(dāng)前實例收到信號,使用 TimeSpan 度量時間間隔并指定是否在等待之前退出同步域。
有了上面的解釋,開始展示代碼(經(jīng)過多次優(yōu)化)
//若要將初始狀態(tài)設(shè)置為終止,則為 true;若要將初始狀態(tài)設(shè)置為非終止,則為 false static AutoResetEvent oddResetEvent = new AutoResetEvent(false); static AutoResetEvent evenResetEvent = new AutoResetEvent(false); static int i = 0; static void Main(string[] args) { //ThreadStart是個委托 Thread thread1 = new Thread(new ThreadStart(show)); thread1.Name = "偶數(shù)線程"; Thread thread2 = new Thread(new ThreadStart(show)); thread2.Name = "奇數(shù)線程"; thread1.Start(); Thread.Sleep(2); //保證偶數(shù)線程先運行。 thread2.Start(); Console.Read(); } public static void show() { while (i <= 100) { int num = i % 2; if (num == 0) { Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "evenResetEvent"); if(i!=1) evenResetEvent.Set(); oddResetEvent.WaitOne(); //當(dāng)前線程阻塞 } else { Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "oddResetEvent"); //如果此時AutoResetEvent 為非終止?fàn)顟B(tài),則線程會被阻止,并等待當(dāng)前控制資源的線程通過調(diào)用 Set 來通知資源可用。否則不會被阻止 oddResetEvent.Set(); evenResetEvent.WaitOne(); } } }
結(jié)果如下圖所示:
注意點:
不要有一點點點點多余的evenResetEvent.Set(),他會讓后續(xù)的 evenResetEvent.WaitOne();失效.
第二種方法Semaphore
此外,我們利用信號量也可以實現(xiàn),信號量是一種內(nèi)核模式鎖,對性能要求比較高,特殊情況下才考慮使用,而且要避免在內(nèi)核模式和用戶模式下頻繁相互切換線程。代碼如下:
private static readonly int MaxSize = 1; private static int i = 0; static Semaphore oddSemaphore = new Semaphore(0, MaxSize); static Semaphore evenSemaphore = new Semaphore(0, MaxSize); static void Main(string[] args) { System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); //ThreadStart是個委托 Thread thread1 = new Thread(new ThreadStart(show)); thread1.Name = "偶數(shù)線程"; Thread thread2 = new Thread(new ThreadStart(show)); thread2.Name = "奇數(shù)線程"; thread1.Start(); thread2.Start(); thread1.Join(); stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds); Console.Read(); } private static void show() { if(i==1) evenSemaphore.WaitOne(); while (i <= 100) { int num = i % 2; if (num == 0) { Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId); evenSemaphore.Release(); oddSemaphore.WaitOne(); //當(dāng)前線程阻塞 } else { Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId); //釋放一個偶數(shù)信號空位出來; oddSemaphore.Release(); evenSemaphore.WaitOne(); //當(dāng)前線程阻塞 //此時已經(jīng)消耗了一個奇數(shù)信號空位 } } }
第三種方法,約定每個線程只干自己的事
這種方法利用線程池本身就是隊列的方式,即先進(jìn)先出。測試之后發(fā)現(xiàn)性能有下降,但是還是貼出來供參考。
static int threadCount = 2; static int count = 0; static object cursorLock = new object(); static void Main(string[] args) { System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); Task[] arr = new Task[2]; for (int threadIndex = 0; threadIndex < threadCount; threadIndex++) { //這兩種方法都可以 arr[threadIndex] = Task.Factory.StartNew(PrintNum, threadIndex); } Task.WaitAll(arr); stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds); Console.Read(); } private static void PrintNum(object num) { bool isOk = false; while (!isOk) { lock (cursorLock) { int index = count % 2; if (count>100) { isOk = true; } else if (index == (int)num) { if (index == 0) Console.WriteLine("{0}:{1} {2} ", "偶數(shù)線程", Thread.CurrentThread.ManagedThreadId, count++); else Console.WriteLine("{0}:{1} {2} ", "奇數(shù)線程", Thread.CurrentThread.ManagedThreadId, count++); } } } }
結(jié)果如下:
第四種方法 Mutex
private static int i = 0; static Mutex mutex = new Mutex(); static void Main(string[] args) { System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); //ThreadStart是個委托 Thread thread1 = new Thread(new ParameterizedThreadStart(show)); thread1.Name = "偶數(shù)線程"; Thread thread2 = new Thread(new ParameterizedThreadStart(show)); thread2.Name = "奇數(shù)線程"; thread1.Start(0); thread2.Start(1); thread2.Join(); stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds); Console.Read(); } /// <summary> /// Mutex的釋放與鎖定 都只能在同一個線程中執(zhí)行 /// </summary> private static void show(object index) { while (i <= 100) { mutex.WaitOne(); int num = i % 2; if (num == (int)index&&i<=100) { Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId); } mutex.ReleaseMutex(); } }
有關(guān)概念資料
http://chabaoo.cn/article/180789.htm
到此這篇關(guān)于C#中AutoResetEvent控制線程用法小結(jié)的文章就介紹到這了,更多相關(guān)AutoResetEvent控制線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#獲取機器碼的方法詳解(機器名,CPU編號,硬盤編號,網(wǎng)卡mac等)
這篇文章主要介紹了C#獲取機器碼的方法,結(jié)合實例形式詳細(xì)分析了C#獲取硬件機器名、CPU編號、硬盤編號、網(wǎng)卡mac等信息的相關(guān)實現(xiàn)方法,需要的朋友可以參考下2016-07-07C#基礎(chǔ)教程之類class與結(jié)構(gòu)struct的區(qū)別
struct是值類型,創(chuàng)建一個struct類型的實例被分配在棧上,class是引用類型,創(chuàng)建一個class類型實例被分配在托管堆上,下面這篇文章主要給大家介紹了關(guān)于C#基礎(chǔ)教程之類class與結(jié)構(gòu)struct區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-11-11C#使用TreeView控件實現(xiàn)的二叉樹泛型節(jié)點類及其方法
TreeView?控件在?C#?中主要用于顯示分層結(jié)構(gòu)的數(shù)據(jù),這通常是一個文件系統(tǒng)的表示,但也可以是任何具有父子關(guān)系的數(shù)據(jù)集合,本文給大家介紹了C#使用TreeView控件實現(xiàn)的二叉樹泛型節(jié)點類及其方法,需要的朋友可以參考下2024-03-03C#值類型、引用類型、泛型、集合、調(diào)用函數(shù)的表達(dá)式樹實踐
本文詳細(xì)講解了C#值類型、引用類型、泛型、集合、調(diào)用函數(shù)的表達(dá)式樹實踐,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-01-01