.NET異步和多線程系列(二)- Thread和ThreadPool

1、Thread類

C#裏面的多線程:Thread類是C#語言對線程對象的一個封裝。html

首先看下如何開啓線程,執行委託的內容:多線程

/// <summary>
/// 一個比較耗時耗資源的私有方法
/// </summary>
private void DoSomethingLong(string name)
{
    Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    long lResult = 0;
    for (int i = 0; i < 1_000_000_000; i++)
    {
        lResult += i;
    }
    Thread.Sleep(2000);
    Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}
/// <summary>
/// 多線程 Thread類是.NET Framework 1.0的時候出現的
/// Thread:C#對線程對象的一個封裝
/// </summary>
private void btnThread_Click(object sender, EventArgs e)
{
    Console.WriteLine($"****************btnThread_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

    {
        ParameterizedThreadStart method = o => this.DoSomethingLong("btnThread_Click");
        Thread thread = new Thread(method);
        thread.Start("浪子天涯");//開啓線程,執行委託的內容
    }

    Console.WriteLine($"****************btnThread_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

線程等待、線程優先級、前臺線程和後臺線程:異步

{
    ThreadStart method = () =>
    {
        Thread.Sleep(5000);
        this.DoSomethingLong("btnThread_Click");
        Thread.Sleep(5000);
    };
    Thread thread = new Thread(method);
    thread.Start(); //開啓線程,執行委託的內容

    //該花括號內的這些方法已經被微軟拋棄了,建議不要去用
    {
        //thread.Suspend(); //暫停
        //thread.Resume();//恢復    真的不應要的,暫停不必定立刻暫停;讓線程操做太複雜了
        //thread.Abort();
        //線程是計算機資源,程序想停下線程,只能向操做系統通知(線程拋異常),
        //會有延時/不必定能真的停下來
        //Thread.ResetAbort();
    }


    //1等待
    while (thread.ThreadState != ThreadState.Stopped)
    {
        Thread.Sleep(200); //當前線程休息200ms
    }

    //2 Join等待
    thread.Join(); //運行這句代碼的線程,等待thread的完成
    thread.Join(1000); //最多等待1000ms

    Console.WriteLine("這裏是線程執行完以後才操做。。。");

    //最高優先級:優先執行,但不表明優先完成看,甚至說極端狀況下,還有意外發生,不能經過這個來控制線程的執行前後順序
    thread.Priority = ThreadPriority.Highest;

    //是不是後臺線程 默認是false
    thread.IsBackground = false; //默認是false 前臺線程,進程關閉,線程須要計算完後才退出
    //thread.IsBackground = true;//關閉進程,線程退出
}

下面來看下Thread類的使用:async

基於Thread封裝一個帶有回調的性能

/// <summary>
/// 基於Thread封裝一個回調
/// 回調:啓動子線程執行動做A--不阻塞--A執行完後子線程會執行動做B
/// </summary>
/// <param name="threadStart">多線程執行的操做</param>
/// <param name="actionCallback">線程完成後,回調的動做</param>
private void ThreadWithCallBack(ThreadStart threadStart, Action actionCallback)
{
    //Thread thread = new Thread(threadStart);
    //thread.Start();
    //thread.Join(); //錯了,由於方法被阻塞了
    //actionCallback.Invoke();

    ThreadStart method = new ThreadStart(() =>
    {
        threadStart.Invoke();
        actionCallback.Invoke();
    });
    new Thread(method).Start();
}
{
    ThreadStart threadStart = () => this.DoSomethingLong("btnThread_Click");
    Action actionCallBack = () =>
    {
        Thread.Sleep(2000);
        Console.WriteLine($"This is Calllback {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    };
    this.ThreadWithCallBack(threadStart, actionCallBack);
}

基於Thread封裝一個帶有返回值的this

/// <summary>
/// 基於Thread封裝一個帶有返回值的
/// 1 異步,非阻塞的
/// 2 還能獲取到最終計算結果
/// 
/// 既要不阻塞,又要計算結果?不可能!故此處返回一個委託,當外部須要使用結果的時候再阻塞,此時可能已經計算完了。
/// </summary>
private Func<T> ThreadWithReturn<T>(Func<T> func)
{
    T t = default(T);
    ThreadStart threadStart = new ThreadStart(() =>
    {
        t = func.Invoke();
    });
    Thread thread = new Thread(threadStart);
    thread.Start();

    return new Func<T>(() =>
    {
        thread.Join();
        //thread.ThreadState
        return t;
    });
}
{
    Func<int> func = () =>
    {
        Thread.Sleep(5000);
        return DateTime.Now.Year;
    };
    Func<int> funcThread = this.ThreadWithReturn(func);//非阻塞
    Console.WriteLine("do something 1");
    Console.WriteLine("do something 2");
    Console.WriteLine("do something 3");
    int iResult = funcThread.Invoke();//阻塞
}

控制線程的數量(僅供參考):spa

{
    List<Thread> threads = new List<Thread>();
    for (int i = 0; i < 100; i++)
    {
        if (threads.Count(t => t.ThreadState == ThreadState.Running) < 10)
        {
            Thread thread = new Thread(new ThreadStart(() => { }));
            thread.Start();
            threads.Add(thread);
        }
        else
        {
            Thread.Sleep(200);
        }
    }
}

2、ThreadPool類

因爲Thread類功能繁多,反而用很差--就像給4歲小孩一把熱武器,反而會形成更大的傷害。並且對線程數量也是沒有管控的。故微軟在.NET Framework 2.0推出來ThreadPool線程池操作系統

若是某個對象建立和銷燬代價比較高,同時這個對象還能夠反覆使用的,就須要一個池子。線程

保存多個這樣的對象,須要用的時候從池子裏面獲取;用完以後不用銷燬,放回池子;(享元模式)code

節約資源提高性能;此外,還能管控總數量,防止濫用;

ThreadPool的線程都是後臺線程。

下面咱們直接來看下相關代碼:

/// <summary>
/// ThreadPool線程池
/// 因爲Thread類功能繁多,反而用很差--就像給4歲小孩一把熱武器,反而會形成更大的傷害
/// 對線程數量是沒有管控的
/// 
/// 線程池是.NET Framework 2.0推出來的
/// 若是某個對象建立和銷燬代價比較高,同時這個對象還能夠反覆使用的,就須要一個池子
/// 保存多個這樣的對象,須要用的時候從池子裏面獲取;用完以後不用銷燬,放回池子;(享元模式)
/// 節約資源提高性能;此外,還能管控總數量,防止濫用;
/// 
/// ThreadPool的線程都是後臺線程
/// 
/// 你們課後能夠試試,基於ThreadPool去封裝回調--返回值的
/// </summary>
private void btnThreadPool_Click(object sender, EventArgs e)
{
    Console.WriteLine($"****************btnThreadPool_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

    //啓動線程
    {
        ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click1"));
        ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click2"), "浪子天涯");
    }

    {
        ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
        Console.WriteLine($"當前電腦最大workerThreads={workerThreads} 最大completionPortThreads={completionPortThreads}");

        ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);
        Console.WriteLine($"當前電腦最小workerThreads={workerThreadsMin} 最大completionPortThreads={completionPortThreadsMin}");

        //設置的線程池數量是進程全局的(慎用,通常不用)
        //委託異步調用--Task--Parrallel--async/await 所有都是線程池的線程
        //直接new Thread不受這個數量限制的(可是會佔用線程池的線程數量)
        ThreadPool.SetMaxThreads(8, 8); //設置的最大值,必須大於CPU核數,不然設置無效
        ThreadPool.SetMinThreads(2, 2);
        Console.WriteLine("====================設置線程池數量最大最小====================");

        ThreadPool.GetMaxThreads(out int workerThreads1, out int completionPortThreads1);
        Console.WriteLine($"當前電腦最大workerThreads={workerThreads1} 最大completionPortThreads={completionPortThreads1}");

        ThreadPool.GetMinThreads(out int workerThreadsMin1, out int completionPortThreadsMin1);
        Console.WriteLine($"當前電腦最大workerThreads={workerThreadsMin1} 最大completionPortThreads={completionPortThreadsMin1}");
    }

    //線程等待
    {
        ManualResetEvent mre = new ManualResetEvent(false);
        //false---關閉---Set打開---true---WaitOne就能經過
        //true---打開--ReSet關閉---false--WaitOne就只能等待
        ThreadPool.QueueUserWorkItem(o =>
        {
            this.DoSomethingLong("btnThreadPool_Click1");
            mre.Set();
        });
        Console.WriteLine("Do Something 1");
        Console.WriteLine("Do Something 2");
        Console.WriteLine("Do Something 3");

        mre.WaitOne();
        Console.WriteLine("任務已經完成了。。。");
    }

    //寫多線程的時候有這麼一種說法:不要阻塞線程池裏面的線程。
    //下面是一個死鎖的例子
    {
        ThreadPool.SetMaxThreads(8, 8);
        ManualResetEvent mre = new ManualResetEvent(false);
        for (int i = 0; i < 10; i++)
        {
            int k = i; //此處必須聲明一個變量存放i的值,不能直接使用i變量,不然會有問題
            ThreadPool.QueueUserWorkItem(t =>
            {
                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId.ToString("00")} show {k}");
                if (k == 9) //設置了最多隻容許8個線程,但此處是9,致使死鎖了
                {
                    mre.Set(); //開關打開
                }
                else
                {
                    mre.WaitOne(); //線程等待,阻塞
                }
            });
        }

        if (mre.WaitOne()) //開關沒打開,一直等待,死鎖了
        {
            Console.WriteLine("任務所有執行成功!");
        }
    }

    Console.WriteLine($"****************btnThreadPool_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

 

Demo源碼:

連接:https://pan.baidu.com/s/1wVscaka37emNGz9x-rm0qA 
提取碼:3l2e

此文由博主精心撰寫轉載請保留此原文連接:https://www.cnblogs.com/xyh9039/p/13550714.html

相關文章
相關標籤/搜索