亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C# 異步多線程入門到精通之ThreadPool篇

 更新時(shí)間:2021年11月22日 14:44:27   作者:菜鳥厚非  
ThreadPool 是 .net 2.0 時(shí)代的產(chǎn)物,有了 Thread 為什么還會(huì)有 ThreadPool 呢?ThreadPool 可以做到限制線程數(shù)量、重用線程

上一篇:C# 異步多線程入門到精通之Thread篇
下一篇:異步多線程之入Task,待更新

啟動(dòng)線程池線程

ThreadPool 提供的 API 相對(duì)于 Thread 是比較少的,在 ThreadPool 中需使用 QueueUserWorkItem 方法,來啟動(dòng)一個(gè)線程

例如:Dosome 是個(gè)普通的方法,傳入 QueueUserWorkItem 方法開啟新線程執(zhí)行此方法

public static void Dosome()
{
    Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
}

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    ThreadPool.QueueUserWorkItem(x => Dosome());

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動(dòng)線程,可以看到新開啟了一個(gè)子線程 3 執(zhí)行任務(wù),而主線程 1 并沒有等待子線程 3

在這里插入圖片描述

線程池線程數(shù)量

在 1.0 時(shí)代的 Thread 是沒有線程數(shù)量概念的,在 ThreadPool 2.0 時(shí)代,線程池線程數(shù)量可以通過 SetMaxThreads、SetMaxThreads 方法設(shè)置最小最大線程。也可以查看線程池線程數(shù)量,以通過 GetMinThreads、GetMaxThreads 方法獲取線程池最小及最大線程數(shù)量。

注意:一般不建議設(shè)置 ThreadPool 線程數(shù)量,這個(gè)操作是全局的。二般情況,當(dāng)線程池線程耗盡,會(huì)造成死鎖。

例如:以通過 SetMaxThreads、SetMaxThreads、GetMinThreads、GetMaxThreads 方法來操作查看線程

{
    ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程
    Console.WriteLine($"【default】最小 workerThreadsMin:{workerThreadsMin}  completionPortThreadsMin:{completionPortThreadsMin}");

    ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程
    Console.WriteLine($"【default】最大 workerThreadsMax:{workerThreadsMax}  completionPortThreadsMax:{completionPortThreadsMax}");
}

ThreadPool.SetMinThreads(3, 3); // 設(shè)置4其實(shí)也不是4,應(yīng)為本機(jī)為邏輯八核,最小也就是這個(gè)
ThreadPool.SetMaxThreads(7, 7);

{
    ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程
    Console.WriteLine($"【自定義】最小 workerThreadsMin:{workerThreadsMin}  completionPortThreadsMin:{completionPortThreadsMin}");

    ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程
    Console.WriteLine($"【自定義】最大 workerThreadsMax:{workerThreadsMax}   completionPortThreadsMax:{completionPortThreadsMax}");
}

ThreadPool.SetMinThreads(5, 5); // 設(shè)置4其實(shí)也不是4,應(yīng)為本機(jī)為邏輯八核,最小也就是這個(gè)
ThreadPool.SetMaxThreads(16, 16);

{
    ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程
    Console.WriteLine($"【自定義】最小 workerThreadsMin:{workerThreadsMin}  completionPortThreadsMin:{completionPortThreadsMin}");

    ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程
    Console.WriteLine($"【自定義】最大 workerThreadsMax:{workerThreadsMax}   completionPortThreadsMax:{completionPortThreadsMax}");
}

在這里插入圖片描述

線程池線程等待

看了前面 ThreadPool 相關(guān)的講解,有小伙伴可能會(huì)發(fā)現(xiàn),我們一直沒有說等待線程,那 ThreadPool 有相關(guān)的 API 嗎?答案:沒有

但可以通過 ManualResetEvent 方式實(shí)現(xiàn)線程等待。一般來說不建議線程等待,二般情況也不建議。應(yīng)為線程池里面,線程數(shù)量有限,寫代碼無意間造成的線程等待沒有釋放,一旦線程池線程耗盡就會(huì)形成死鎖。除非非得等待情況,但記得一定要釋放等待,多多檢查代碼。

例如:線程等待,ManualResetEvent 初始化為 false,Set() 方法會(huì)設(shè)置為 true,WaitOne() 方法會(huì)檢查 ManualResetEvent 對(duì)象是否為 true,如果不為會(huì)一直等待,如果為 true 會(huì)直接過去

public static void Dosome()
{
    Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
    Thread.Sleep(5 * 1000); // 模擬任務(wù)耗時(shí)
    Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
}

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    ManualResetEvent manualResetEvent = new ManualResetEvent(false);
    ThreadPool.QueueUserWorkItem(x =>
    {
        Dosome();
        manualResetEvent.Set(); // 會(huì)變成 true
    });
    manualResetEvent.WaitOne();

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動(dòng)程序,可以看到主線程 1 等待 子線程 3 執(zhí)行完成后,在執(zhí)行了 Main 方法結(jié)束代碼

在這里插入圖片描述

例如:線程耗盡形成死鎖,首先對(duì)線程池線程數(shù)量進(jìn)行了限制,最大為 10 個(gè)線程。接著我們循環(huán)啟動(dòng) 18 個(gè)線程工作,且讓前 18 個(gè)線程形成等待。

 Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

 ThreadPool.SetMinThreads(4, 4);
 ThreadPool.SetMaxThreads(10, 10);
 ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程
 Console.WriteLine($"【自定義】最小 workerThreadsMin:{workerThreadsMin}  completionPortThreadsMin:{completionPortThreadsMin}");
 ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程
 Console.WriteLine($"【自定義】最大 workerThreadsMax:{workerThreadsMax}   completionPortThreadsMax:{completionPortThreadsMax}");

 ManualResetEvent manualResetEvent = new ManualResetEvent(false);
 for (int i = 0; i < 20; i++)
 {
     int k = i;
     ThreadPool.QueueUserWorkItem((x) =>
     {
         Console.WriteLine(k);
         if (k < 18)
         {
             manualResetEvent.WaitOne();
         }
         else
         {
             manualResetEvent.Set();
         }
     });
 }
 manualResetEvent.WaitOne();

 Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

 Console.ReadLine();

啟動(dòng)程序,可以看到,當(dāng)開啟 10 個(gè)線程后,程序就已經(jīng)不再運(yùn)行了。這是當(dāng)程循環(huán)開啟第 11 個(gè)子線程時(shí),發(fā)現(xiàn)線程池里面沒有線程了,就會(huì)一直等待,這樣一個(gè)狀態(tài)就是死鎖。

在這里插入圖片描述

線程回調(diào)

講到現(xiàn)在,細(xì)心的小伙伴會(huì)發(fā)現(xiàn)一直沒有說線程回調(diào),即當(dāng)子線程執(zhí)行一個(gè)任務(wù)完成后,再執(zhí)行一個(gè)任務(wù)。其實(shí) Thread 與 ThreadPool 都沒有回調(diào),但是可以創(chuàng)造出 Callback,那就是包一層,如果不行那就再包一層。

Thread

例如:創(chuàng)建一個(gè)普通方法 ThreadWithCallback 傳入兩個(gè)委托參數(shù),一個(gè)實(shí)際任務(wù),一個(gè) Callback。接著在內(nèi)部使用 Thread 開啟一個(gè)新的線程,執(zhí)行 action、callback 方法。

private static void ThreadWithCallback(Action action, Action callback)
{
    Thread thread = new Thread(() =>
    {
        action.Invoke();
        callback.Invoke();
    });
    thread.Start();
}

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    ThreadWithCallback(() =>
    {
        Console.WriteLine($"action,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
    }, () =>
    {
        Console.WriteLine($"callback,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
    });

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動(dòng)程序,可以看到 action 執(zhí)行后,再執(zhí)行了 callback

在這里插入圖片描述

ThreadPool

例如:創(chuàng)建一個(gè)普通方法 ThreadWithCallback 傳入兩個(gè)委托參數(shù),一個(gè)實(shí)際任務(wù),一個(gè) Callback。接著在內(nèi)部使用 ThreadPool 開啟一個(gè)新的線程,執(zhí)行 action、callback 方法。

private static void ThreadWithCallback(Action action, Action callback)
{
    ThreadPool.QueueUserWorkItem(x =>
    {
        action.Invoke();
        callback.Invoke();
    });
}

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    ThreadWithCallback(() =>
    {
        Console.WriteLine($"action,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
    }, () =>
    {
        Console.WriteLine($"callback,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
    });

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動(dòng)程序,可以看到 action 執(zhí)行后,再執(zhí)行了 callback

在這里插入圖片描述

線程返回值

講到現(xiàn)在,細(xì)心的小伙伴會(huì)發(fā)現(xiàn)一直沒有說線程返回值,在 1.0、2.0 時(shí)代的 Thread、ThreadPool 是沒有提供相關(guān) API 的。但是可以創(chuàng)造出來,還是包一層,如果不行那就再包一層。

Thread

例如:創(chuàng)建一個(gè)普通方法 ThreadWithReturnCallback 返回與入?yún)⒍际?Func< T >,內(nèi)部啟用一個(gè) Thread 執(zhí)行委托,return 一個(gè)帶返回值的委托且 Thread 的線程等待放置里面。使用時(shí)給 ThreadWithReturnCallback 方法傳入帶返回值的委托即可,因?yàn)?ThreadWithReturnCallback 方法返回值也是委托,所以要想獲得結(jié)果需要在外部 Invoke 一下,這個(gè) Invoke 操作會(huì)卡主線程。

private static Func<T> ThreadWithReturnCallback<T>(Func<T> func)
{
    T t = default(T);
    Thread thread = new Thread(() =>
    {
        t = func.Invoke();
    });
    thread.Start();

    return () =>
    {
        thread.Join();
        return t;
    };
}

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Func<int> func = ThreadWithReturnCallback<int>(() =>
    {
        return DateTime.Now.Millisecond;
    });

    int iResult = func.Invoke();
    Console.WriteLine(iResult);

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

在這里插入圖片描述

ThreadPool

例如:創(chuàng)建一個(gè)普通方法 ThreadPoolWithReturnCallback 返回與入?yún)⒍际?Func< T >,使用 QueueUserWorkItem 方法啟動(dòng)線程執(zhí)行委托,因?yàn)?ThreadPool 本身并未提供線程等待方法,所以這里使用 ManualResetEvent 進(jìn)行線程等待,return 一個(gè)帶返回值的委托且 ManualResetEvent WaitOne 線程等待放置里面。使用時(shí)給 ThreadPoolWithReturnCallback 方法傳入帶返回值的委托即可,因?yàn)?ThreadPoolWithReturnCallback 方法返回值也是委托,所以要想獲得結(jié)果需要在外部 Invoke 一下,這個(gè) Invoke 操作會(huì)卡主線程。

private static Func<T> ThreadPoolWithReturnCallback <T>(Func<T> func)
{
    T t = default(T);

    ManualResetEvent manualResetEvent = new ManualResetEvent(false);

    ThreadPool.QueueUserWorkItem(x =>
    {
        t = func.Invoke();
        manualResetEvent.Set(); // 會(huì)變成 true
    });

    return () =>
    {
        manualResetEvent.WaitOne();    
        return t;
    };
}

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Func<int> func = ThreadPoolWithReturnCallback <int>(() =>
    {
        return DateTime.Now.Millisecond;
    });

    int iResult = func.Invoke();
    Console.WriteLine(iResult);

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

在這里插入圖片描述

線程池線程重用

在 1.0 時(shí)代的 Thread 每次創(chuàng)建實(shí)例都會(huì)向操作系統(tǒng)申請(qǐng)線程,2.0 時(shí)代的 ThreadPool 每次使用 QueueUserWorkItem 都會(huì)向線程池拿取線程,并不會(huì)向操作系統(tǒng)申請(qǐng)線程。所以,使用 ThreadPool 創(chuàng)建線程的效率是高于 Thread 的。

例如:我們開啟三波線程執(zhí)行任務(wù),執(zhí)行相同的任務(wù)

static void Main(string[] args)
{
    Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}\n");

    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"張三,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"李四,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"王五,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"麻溜,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });

    Thread.Sleep(1000);Console.WriteLine();

    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"張三,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"李四,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"王五,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"麻溜,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });

    Thread.Sleep(1000); Console.WriteLine();

    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"張三,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"李四,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"王五,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); });
    ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"麻溜,任務(wù)處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}\n"); });

    Thread.Sleep(1000);

    Console.WriteLine($"Main 方法結(jié)束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動(dòng)程序,第一波的時(shí)候啟用了 3、4、6、7,第二波重用了第一波的 6、7、第三波重用了第一、第二波的 3、4、5、8。其中未重用的呢,是線程并未回收(回收需要時(shí)間),所以未重用。

在這里插入圖片描述

擴(kuò)展知識(shí)-委托線程

委托的 BeginInvoke 方法使用的是線程池的線程,在任務(wù)執(zhí)行完成后,子線程時(shí)不會(huì)被立馬回收的,除非調(diào)用 EndInvoke 可以立馬結(jié)束子線程回到線程池,利于線程更好的重用。

例如:BeginInvoke 線程不能立馬被回收重用

static void Main(string[] args)
{
    Action<int> action = x =>
    {
        Console.WriteLine($"我是 {x},Thread:{Thread.CurrentThread.ManagedThreadId}");
    };

    for (int i = 0; i < 5; i++)
    {
        action.BeginInvoke(i,null,null);
    }

    Console.ReadLine();
}

啟動(dòng)線程,并發(fā)五次,分別啟用了4、5、7、8、9,五個(gè)線程

在這里插入圖片描述

例如:EndInvoke 線程更好重用,BeginInvoke 方法的第二個(gè)參數(shù)

static void Main(string[] args)
{
    Action<int> action = x =>
    {
        Console.WriteLine($"我是 {x},Thread:{Thread.CurrentThread.ManagedThreadId}");
    };

    for (int i = 0; i < 5; i++)
    {
        action.BeginInvoke(i, e => { action.EndInvoke(e); }, null);
    }

    Console.ReadLine();
}

啟程序,可以看到并發(fā) 5 次只使用了,線程 3 與 8。

在這里插入圖片描述

到此這篇關(guān)于C# 異步多線程入門到精通之ThreadPool篇的文章就介紹到這了,更多相關(guān)C# ThreadPool內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論