天天看点

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#基础之 多线程的前世今生(中) 进阶篇