ShowProgramCode

2022年12月15日 星期四

C# .Net Core6 多執行緒處理ThreadPool

現行遇到一個狀況,需要使用多執行緒連線TCP Server。
原先我是想使用Thread,不過在查詢如何使用多個執行緒設定時,發現了另外一個可用的方法ThreadPool

首先是Microsoft目前並不建議直接使用Thread控制,加上ThreadPool可以直接在for迴圈內使用,自動增加多個執行緒。對於我目前要做一個壓測用的程式而言,只要在設定檔寫好測試次數或者測試時間,用for迴圈執行ThreadPool就能得到想要的結果。實在是太方便了!

不過,在這當中我遇到了一些問題,為防止自己忘記,先記錄在此。

  1.  依照說明傳入是非固定的object,該如何設為固定的物件?
  2.  設定的執行緒的最大值,但實際程式執行時,同一時間內的執行緒超過上限...

首先是關於第一項的問題,該如何將object轉為固定的物件?
關於這一點,必須先說明ThreadPool如何使用...
下面的範例是我用來測試使用的,不過後來改過,所以不確定這個是否可以執行...

static void Main(string[] args)
{
    for(var i=0;i<100;i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(countIndex), i);
    }
}

private static void countIndex(object? obj)
{
    string timeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
    string msg = $"{timeStr}\t[{obj}] \t[{Thread.CurrentThread.ManagedThreadId}] \t This is Test!!";
    Console.WriteLine(msg);
}

測試沒有問題後,我發現我要帶入的是一個完整的資料格式。
我用過不少方法,都沒有成功,最後改為下面的方式就可以執行了。
不過必須要用try catch包好,物件轉換錯誤可能會讓程式整個當掉,但因為是內部使用,就沒寫那麼多防呆了。

static void Main(string[] args)
{
    for(var i=0;i<100;i++)
    {
        DTO dto = new dto;
        dto.Index = i;
        ...
        ThreadPool.QueueUserWorkItem(new WaitCallback(countIndex), dto);
    }
}

private static void countIndex(object? obj)
{
    try
    {
        //這裡的轉換物件須注意
        DTO dto = (DTO)obj;
        ...
    	string msg = getMessage(obj.Index, dto.Message);
    	Console.WriteLine(msg);
    }
    catch (Exception ex)
    {
        string msg = getMessage(0, $"error = {ex.ToString()}");
        Console.WriteLine(msg);
    }
}

private static string getMessage(int Index, string msg)
{
    string timeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
    return $"{timeStr}\t[{Index}] \t[{Thread.CurrentThread.ManagedThreadId}] \t {msg}";
}

其次是關於執行緒上限的設定...
依照我上方的程式碼,執行緒ID與Index是可以做比對的,所以我發現了在同一時間,執行緒超出我設定的上限...
當時的程式碼大致如下:

static void Main(string[] args)
{
    ThreadPool.SetMaxThreads(5, 5);
    for(var i=0;i<100;i++)
    {
        DTO dto = new dto;
        dto.Index = i;
        ...
        ThreadPool.QueueUserWorkItem(new WaitCallback(countIndex), dto);
    }
}

private static void countIndex(object? obj)
{
    try
    {
        //這裡的轉換物件須注意
        DTO dto = (DTO)obj;
        ...
    	string msg = getMessage(obj.Index, dto.Message);
    	Console.WriteLine(msg);
    }
    catch (Exception ex)
    {
        string msg = getMessage(0, $"error = {ex.ToString()}");
        Console.WriteLine(msg);
    }
}

private static string getMessage(int Index, string msg)
{
    string timeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
    return $"{timeStr}\t[{Index}] \t[{Thread.CurrentThread.ManagedThreadId}] \t {msg}";
}

但是實際執行,跳出的執行緒至少超過10個...
這和我當初想像的結果完全不同,測試過許多方法都沒有用,最後才發現只設定SetMaxThreads是不行的!
將程式碼調整成下方後,測試的結果終於與我想的相同了。Thread.CurrentThread.ManagedThreadId出現的號碼只有設定的數量,同一個時間執行緒也不會超過上限...

static void Main(string[] args)
{
    //min & max 必須同時設定才有效...
    ThreadPool.SetMinThreads(2, 2);
    ThreadPool.SetMaxThreads(5, 5);
    for(var i=0;i<100;i++)
    {
        DTO dto = new dto;
        dto.Index = i;
        ...
        ThreadPool.QueueUserWorkItem(new WaitCallback(countIndex), dto);
    }
}

private static void countIndex(object? obj)
{
    try
    {
        //這裡的轉換物件須注意
        DTO dto = (DTO)obj;
        ...
    	string msg = getMessage(obj.Index, dto.Message);
    	Console.WriteLine(msg);
    }
    catch (Exception ex)
    {
        string msg = getMessage(0, $"error = {ex.ToString()}");
        Console.WriteLine(msg);
    }
}

private static string getMessage(int Index, string msg)
{
    string timeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
    return $"{timeStr}\t[{Index}] \t[{Thread.CurrentThread.ManagedThreadId}] \t {msg}";
}

參考網址:
Microsoft官網說明
kinanson的技術回憶
安德魯的部落格
余小章 @ 大內殿堂
玩轉C#之【執行序-實際實作】
C# .NET Blazor MAUI Xamarin Research

沒有留言:

張貼留言