C# 多線程通信詳解


一、WaitHandler的類層次

image

可以看到 WaitHandle是 事件(EventWaitHandle)、互斥體(Mutex)、信號量(Sempahore)的父類。

image

 

image

 

WaitHandle我們最經常使用的方法,並是使用它的靜態方法WaitAll. 我們會發現在這個WaitHandle里面只有等待方法,也就是它會阻塞當前線程的執行。

那么如何要解除它對當前線程的阻塞呢,那么就需要依賴於各個子類的方法了。

image

image

image

 

例如現在有一個這樣的場景,如何在一個方法中,等待所有的線程全部執行完,最后再統計得到的計算結果呢?

WaitHandle[] handlers = new WaitHandle[]{
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false)
};

for (var i = 0; i < handlers.Length; i++)
{
    ThreadPool.QueueUserWorkItem(ar =>
    {
        int index = (int)ar;
        Thread.Sleep(1000);
        AppCenter.AppendLog("任務:" + index + "開始執行!");
        (handlers[index] as AutoResetEvent).Set();
    }, i);
}

ThreadPool.QueueUserWorkItem(ar =>
{
    WaitHandle.WaitAll(handlers);
    AppCenter.AppendLog("所有任務都已經完成了,我不用再等待了。");
});
運行結果如下:

image

二、EventWaitHandle

image

這個方法,可以方便實現兩個線程之間的相互通信。

如何實現兩個線程的相互通信?

EventWaitHandle handleA = new AutoResetEvent(false);
EventWaitHandle handleB = new AutoResetEvent(false);

ThreadPool.QueueUserWorkItem(ar =>
{
    AppCenter.AppendLog("A:我是A,我已經開始運行了");
    Thread.Sleep(2000);
    AppCenter.AppendLog("A:我想睡覺了,B你先跑跑吧。");
    EventWaitHandle.SignalAndWait(handleB, handleA);
    AppCenter.AppendLog("A:開始工作ing");
    Thread.Sleep(3000);
    AppCenter.AppendLog("A:這個有點難,問下B");
    EventWaitHandle.SignalAndWait(handleB, handleA);
    AppCenter.AppendLog("A:不錯,今天任務搞定,我也閃人了。");
});

ThreadPool.QueueUserWorkItem(ar =>
{
    handleB.WaitOne();
    AppCenter.AppendLog("B:我是B,我已經頂替A開始運行了。");
    Thread.Sleep(5000);
    AppCenter.AppendLog("B:我的事情已經做完了,該讓A搞搞了,休息一會。");
    EventWaitHandle.SignalAndWait(handleA, handleB);
    AppCenter.AppendLog("B:hi,A我搞定了,下班了。");
    handleA.Set();
});
運行結果如下:

image

 

那么AutoResetEvent和ManualResetEvent有什么區別呢?我們先做個實驗。

private EventWaitHandle manualEvent = new ManualResetEvent(false);

private void ManualResetEvent_Click(object sender, EventArgs e)
{   

    AppCenter.CleanLogs();

    ThreadPool.QueueUserWorkItem(ar =>
    {
        int i = 0;
        while (true)
        {
            manualEvent.WaitOne();  //ManualResetEvent的Set()方法,讓事件的終止狀態永遠為true,讓這里一直能執行。             i++;                    //而AutoResetEvent的Set()方法,初始化讓這里執行一次,然后再次執行時是非終止的。將阻塞原有線程的執行
            AppCenter.AppendLog("#" + i.ToString());
            Thread.Sleep(1000);
        }
    });
}

private void button2_Click(object sender, EventArgs e)
{
    manualEvent.Set();
}

private void button3_Click(object sender, EventArgs e)
{
    manualEvent.Reset();
}

運行結果

image

我們會發現ManualResetEvent在觸發Set()方法會,解除了原有的線程的 WaitOne方法,會一直打印輸出。

而當我們替換為AutoResetEvent方法時候。

image

 

此時每次只會打印一個輸出。因為它將 事件的狀態設置為終止后,又變為了false.

 

三、Semaphore 控制並行線程的執行

應用場景,如果有多個線程跑,我能否每次控制3個線程一起跑呢。

Semaphore sempore = new Semaphore(0, 3);
for (int i = 0; i < 8; i++)
{
    ThreadPool.QueueUserWorkItem(ar =>
    {
        sempore.WaitOne();
        AppCenter.AppendLog("\t第:" +((int)ar).ToString() + "個開始運行.");
    },i);
}
ThreadPool.QueueUserWorkItem(ar =>
{
    for (int i = 0; i < 3; i++)
    {
        AppCenter.AppendLog("" + (i + 1).ToString() + "批開始執行.");
        sempore.Release(3);
        Thread.Sleep(5000);
    }
});

運行結果:

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM