天天看點

Unity C#基礎之 多線程的前世今生(中) 進階篇

書接上回 Unity C#基礎之 多線程的前世今生(上) 科普篇

,下面為大家介紹從.NET 1.X到4.X版本中多線程的示例,推薦使用.NET 3.X版本

注意:列印提示中資訊很重要,回報出線程ID和順序,便于了解

歡迎大家留言交流

  • 【2018.06.20更新】Task.Delay(millisecond) .ContinueWith(t=>{DoSomething();}) //task延遲執行,不卡主線程

示例工程下載下傳

.NET 1.X版本

  • Thread的基本使用
  • 基于Thread封裝支援回調
  • 基于Thread封裝支援回調帶傳回值
/// <summary>
    /// 多線程1.0  1.1
    /// </summary>
    private void ThreadsOnClick()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ThreadsOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        {
            Action act = new Action(() => this.DoSomethingLong("ThreadsOnClick"));
            ThreadStart threadStart = new ThreadStart(() =>
            {
                Thread.Sleep(5000);
                this.DoSomethingLong("ThreadsOnClick");
            });
            Thread thread = new Thread(threadStart)
            {
                IsBackground = true//變成背景線程
            };//Action和ThreadStart 限制一緻,但是不同類型
            thread.Start();//預設是前台線程,UI線程退出後,還會繼續執行完;背景線程就直接退出了

            //thread.Suspend();
            //thread.Resume();
            //thread.Join();//做等待
            //thread.Abort();
            //while (thread.ThreadState != System.Threading.ThreadState.Running)
            //{
            //}
        }
        watch.Stop();
        Debug.Log($"ThreadsOnClick End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
           
列印結果如下
Unity C#基礎之 多線程的前世今生(中) 進階篇
private void ThreadsOnClick01()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ThreadsOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        this.ThreadWithCallback(() =>
            {
                Thread.Sleep(2000);
                Debug.Log($"這裡是ThreadWithCallback 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            }
            , () =>
             {
                 Thread.Sleep(2000);
                 Debug.Log($"這裡是ThreadWithCallback回調 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
             });

        watch.Stop();
        Debug.Log($"ThreadsOnClick End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }

    /// <summary>
    /// 基于Thread封裝支援回調
    /// BeginInvoke的回調
    /// </summary>
    /// <param name="threadStart"></param>
    /// <param name="callback"></param>
    private void ThreadWithCallback(ThreadStart threadStart, Action callback)
    {
        ThreadStart startNew = new ThreadStart(
            () =>
            {
                threadStart();
                callback.Invoke();
            });
        Thread thread = new Thread(startNew);
        thread.Start();
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇
private void ThreadsOnClick02()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ThreadsOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        Func<int> func = this.ThreadWithReturn(() =>//begininvoke
        {
            Thread.Sleep(2000);
            Debug.Log($"這裡是ThreadStart {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            return 12345;
        });
        int iResult = func.Invoke();//相當于異步endinvoke
        watch.Stop();
        Debug.Log($"ThreadsOnClick End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
    /// <summary>
    /// 基于Thread封裝支援回調帶傳回值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="funcT"></param>
    /// <returns></returns>
    private Func<T> ThreadWithReturn<T>(Func<T> funcT)
    {
        T t = default(T);
        ThreadStart startNew = new ThreadStart(
            () =>
            {
                t = funcT.Invoke();
            });
        Thread thread = new Thread(startNew);
        thread.Start();

        return new Func<T>(() =>
        {
            thread.Join();
            return t;
        });
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇

.NET 2.X版本

  • ThreadPool.QueueUserWorkItem
  • ManualResetEvent
  • ThreadPool.SetMaxThreads等參數設定
/// <summary>
    /// 線程池 2.0   封裝
    /// 1 去掉各種 api 避免濫用,降低複雜度
    /// 2 池化:1減少建立/銷毀的成本 2 限制最大線程數量
    /// </summary>
    private void ThreadPoolOnClick()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ThreadPoolOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        {
            ThreadPool.QueueUserWorkItem(
                o =>
                {
                    new Action(() =>
                    {
                        this.DoSomethingLong("Action:ThreadPoolOnClick");
                        //回調  就包一層,放在這裡
                    }).Invoke();
                    Debug.Log("1234356786954");
                });

            ManualResetEvent mre = new ManualResetEvent(false); //相當于WaitOne() 、Join()一種等待狀态,會阻礙主線程 狀态變為True開始執行下一行Code

            ThreadPool.QueueUserWorkItem(o =>
            {
                this.DoSomethingLong("ThreadPoolOnClick");
                Debug.Log(o.ToString());
                mre.Set();
            }, "QueueUserWorkItem.State");

            Debug.Log("before WaitOne");
            mre.WaitOne();
            Debug.Log("after WaitOne");
        }

        watch.Stop();
        Debug.Log($"ThreadPoolOnClick End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇
private void ThreadPoolOnClick01()
    {
        #region ManualResetEvent
        //沒有需求。就别等待 阻塞線程
        {
            ManualResetEvent mre = new ManualResetEvent(false);//false 關閉
            new Action(() =>
            {
                Thread.Sleep(1000);
                Debug.Log($"委托的異步調用01  線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 時間點: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                mre.Set();//打開
            }).BeginInvoke(null, null);

            mre.WaitOne();
            Debug.Log(" mre.WaitOne()狀态為打開");
            mre.Reset();//關閉
            new Action(() =>
            {
                Thread.Sleep(1000);
                Debug.Log($"委托的異步調用02  線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 時間點: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                mre.Set();//打開
            }).BeginInvoke(null, null);
            mre.WaitOne();
            Debug.Log("結束");
        }
        #endregion
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇
public void ThreadPoolSetParameter()
    {
        #region PoolSet
        ThreadPool.SetMaxThreads(8, 8);//最小也是核數
        ThreadPool.SetMinThreads(8, 8);
        int workerThreads = 0;
        int ioThreads = 0;
        ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
        Debug.Log(String.Format("Max worker threads: {0};    Max I/O threads: {1}", workerThreads, ioThreads));

        ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
        Debug.Log(String.Format("Min worker threads: {0};    Min I/O threads: {1}", workerThreads, ioThreads));

        ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
        Debug.Log(String.Format("Available worker threads: {0};    Available I/O threads: {1}", workerThreads, ioThreads));
        #endregion
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇

.NET 3.X版本 (推薦)

  • Task.Run&&TaskFactory 和狀态等待
  • ContinueWith
  • Parallel

/// <summary>
    /// Task 3.0   
    /// 使用的是線程池的線程  全部是背景線程
    /// API很強大
    /// 
    /// 多線程:業務是可以并發執行
    /// 
    /// 千萬不要在Task裡面去啟動Task
    /// </summary>
    private void TaskOnClick()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"****************TaskOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

        Task t1 = Task.Run(new Action(() =>
        {
            this.DoSomethingLong("Task.Run:TaskOnClick");
        }));

        TaskFactory taskFactory = Task.Factory;//等同于 new TaskFactory();
        List<Task> taskList = new List<Task>();
        taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_001")));
        taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_002")));
        taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_003")));
        //回調
        //taskList.Add(taskFactory.ContinueWhenAny(taskList.ToArray(), t => Debug.Log($"這裡是ContinueWhenAny {Thread.CurrentThread.ManagedThreadId.ToString("00")}")));//ContinueWhenAny其中任何一個線程完成回調 異步檢測完成不卡界面
        //taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Debug.Log($"這裡是ContinueWhenAll {Thread.CurrentThread.ManagedThreadId.ToString("00")}")));//ContinueWhenAll所有線程完成回調 異步檢測完成不卡界面
        taskFactory.ContinueWhenAny(taskList.ToArray(), t => Debug.Log($"這裡是ContinueWhenAny {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));//ContinueWhenAny其中任何一個線程完成回調 異步檢測完成不卡界面
        taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Debug.Log($"這裡是ContinueWhenAll {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));//ContinueWhenAll所有線程完成回調 異步檢測完成不卡界面

        //需要多線程加快速度  同時又要求某個完成後,才能傳回
        //多業務操作  希望并發,,但是某個完成後,才能傳回
        //Task.WaitAny(taskList.ToArray());//卡界面
        //Debug.Log("某個任務都完成,才會執行");

        //需要多線程加快速度  同時又要求全部完成後,才能傳回
        //多業務操作  希望并發,,但是全部完成後,才能傳回
        //Task.WaitAll(taskList.ToArray());//卡界面
        //Debug.Log("全部任務都完成,才會執行");

        watch.Stop();
        Debug.Log($"****************TaskOnClick End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms***************");
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇

private void TaskOnClick01()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"****************TaskOnClick01 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

        TaskFactory taskFactory = Task.Factory;//等同于 new TaskFactory();
        Task task = taskFactory.StartNew(t => this.DoSomethingLong("btnTask_Click_005"), "菜鳥海瀾").ContinueWith(t => Debug.Log($"這裡是{t.AsyncState}的回調"));

        Task<int> intTask = taskFactory.StartNew(() => 123);
        int iResult = intTask.Result;
        Debug.Log($"線程結果{iResult}");

        watch.Stop();
        Debug.Log($"****************TaskOnClick01 End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms***************");
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇

.NET 4.X版本

  • Parallel.Invoke
  • Parallel.For
  • Parallel.ForEach
  • ParallelOptions&&ParallelLoopState

/// <summary>
    /// Parallel
    /// </summary>
    private void ParallelOnClick()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ParallelOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        //跟task很像,==task+waitall 卡界面 啟動多個線程計算   主線程也參與計算,就是節約了一個線程
        Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_001"),
            () => this.DoSomethingLong("btnParallel_Click_002"),
            () => this.DoSomethingLong("btnParallel_Click_003"),
            () => this.DoSomethingLong("btnParallel_Click_004"),
            () => this.DoSomethingLong("btnParallel_Click_005"));

        watch.Stop();
        Debug.Log($"ParallelOnClick End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
           
列印結果如下,主線程參與計算
Unity C#基礎之 多線程的前世今生(中) 進階篇

private void ParallelOnClick01()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ParallelOnClick01 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        Parallel.For(0, 5, t =>
          {
              this.DoSomethingLong($"btnParallel_Click_00{t}");
          });

        watch.Stop();
        Debug.Log($"ParallelOnClick01 End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇

private void ParallelOnClick02()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ParallelOnClick02 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, t =>
                {
                    this.DoSomethingLong($"btnParallel_Click_00{t}");
                });

        watch.Stop();
        Debug.Log($"ParallelOnClick02 End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇

private void ParallelOnClick03()
    {
        System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        watch.Start();
        Debug.Log($"ParallelOnClick03 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        ParallelOptions options = new ParallelOptions()
        {
            MaxDegreeOfParallelism = 3//最大并行數量(線程數)
        };

        //Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, t =>
        //{
        //    this.DoSomethingLong($"btnParallel_Click_00{t}");
        //});

        Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, (t, state) =>
        {
            this.DoSomethingLong($"btnParallel_Click_00{t}");

            if (t == 4)
            {
                /*
                 * 調用Stop方法表明,尚未啟動的循環的任何疊代都不需要運作。它有效地取消了循環的任何額外疊代。但是,它并沒有停止任何已經開始執行的疊代。
                 * 
                 */
                state.Stop();//沒有運作的線程會取消結束
                Debug.Log($"Stop全部線程 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                return;
            }
            if (state.IsStopped)
            {
                Debug.Log($"已經被其他線程調用Stop 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                return;
            }
            /*
             * //break 與stop同時使用可能觸發異常:InvalidOperationException: Stop was called after Break was called
             * https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallelloopstate.break(v=vs.100).aspx
             * https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallelloopstate.stop.aspx
             */
            // if (t == 2) 
            //{
            //    Debug.Log($"Break線程 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            //    state.Break();//停止目前的
            //    return;
            //}
        });

        watch.Stop();
        Debug.Log($"ParallelOnClick03 End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}  耗時{watch.ElapsedMilliseconds}ms");
    }
           
Unity C#基礎之 多線程的前世今生(中) 進階篇