Unity C#基础之 多线程的前世今生(中) 进阶篇

简介: 书接上回Unity C#基础之 多线程的前世今生(上) 科普篇,下面为大家介绍从.NET 1.X到4.X版本中多线程的示例,推荐使用.NET 3.X版本注意:打印提示中信息很重要,反馈出线程ID和顺序,便于理解欢迎大家留言交流【2018.

书接上回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封装支持回调带返回值
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");
    }
打印结果如下
img_778d801d1de82e59a19073948c0d9003.png
基于Thread封装支持回调
    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();
    }
打印结果如下
img_172303ed63086dc923a53f3c31049f7e.png
基于Thread封装支持回调带返回值
    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;
        });
    }
打印结果如下
img_e1596408bd35c489e174b8892506394f.png

.NET 2.X版本

  • ThreadPool.QueueUserWorkItem
  • ManualResetEvent
  • ThreadPool.SetMaxThreads等参数设置
ThreadPool.QueueUserWorkItem
    /// <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");
    }
打印结果如下
img_c5f7cc323bd8b68c15790dcf3d945266.png
ManualResetEvent
    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
    }
打印结果如下
img_7e4f4add60ac856da014b54157cf97b5.png
ThreadPool.SetMaxThreads等参数设置
    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
    }
打印结果如下
img_d0a8f26e52c8dd0915f4831a4a076ee6.png

.NET 3.X版本 (推荐)

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

Task.Run&&TaskFactory 和状态等待

    /// <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***************");
    }
打印结果如下
img_f15e9e502d06a2d7e09a76c497b7580c.png

ContinueWith

    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***************");
    }
打印结果如下
img_84f64fd121cc13321ed3c650498dfa5b.png

.NET 4.X版本

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

Parallel.Invoke

    /// <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");
    }
打印结果如下,主线程参与计算
img_31049cfdb9ef90e9381bd5ff87c62afc.png

Parallel.For

    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");
    }
打印结果如下
img_6cd14c3cf51acdf3d83257051dde936c.png

Parallel.ForEach

    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");
    }
打印结果如下
img_79b354f9cbc1a49e2dbb0e72d2c03443.png

ParallelOptions&&ParallelLoopState

    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");
    }
打印结果如下
img_ecdad9df699af23c980aaf7e8cefb7d1.png
相关文章
|
3月前
|
数据采集 XML JavaScript
C# 中 ScrapySharp 的多线程下载策略
C# 中 ScrapySharp 的多线程下载策略
|
2月前
|
安全 数据库连接 API
C#一分钟浅谈:多线程编程入门
在现代软件开发中,多线程编程对于提升程序响应性和执行效率至关重要。本文从基础概念入手,详细探讨了C#中的多线程技术,包括线程创建、管理及常见问题的解决策略,如线程安全、死锁和资源泄露等,并通过具体示例帮助读者理解和应用这些技巧,适合初学者快速掌握C#多线程编程。
76 0
|
3月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
73 0
|
3月前
|
安全 C# 开发者
【C# 多线程编程陷阱揭秘】:小心!那些让你的程序瞬间崩溃的多线程数据同步异常问题,看完这篇你就能轻松应对!
【8月更文挑战第18天】多线程编程对现代软件开发至关重要,特别是在追求高性能和响应性方面。然而,它也带来了数据同步异常等挑战。本文通过一个简单的计数器示例展示了当多个线程无序地访问共享资源时可能出现的问题,并介绍了如何使用 `lock` 语句来确保线程安全。此外,还提到了其他同步工具如 `Monitor` 和 `Semaphore`,帮助开发者实现更高效的数据同步策略,以达到既保证数据一致性又维持良好性能的目标。
42 0
|
3月前
|
C# 图形学 数据安全/隐私保护
Unity数据加密☀️ 二、使用Rider将C#代码生成DLL文件
Unity数据加密☀️ 二、使用Rider将C#代码生成DLL文件
|
5月前
|
C# 图形学 C++
使用vscode开发C#+unity没有代码提示问题
使用vscode开发C#+unity没有代码提示问题
74 0
使用vscode开发C#+unity没有代码提示问题
|
5月前
|
并行计算 算法 C#
C# Mandelbrot和Julia分形图像生成程序更新到2010-9-14版 支持多线程计算 多核处理器
此文档是一个关于分形图像生成器的介绍,作者分享了个人开发的M-J算法集成及色彩创新,包括源代码和历史版本。作者欢迎有兴趣的读者留言交流,并提供了邮箱(delacroix_xu@sina.com)以分享资源。文中还展示了程序的发展历程,如增加了真彩色效果、圈选放大、历史记录等功能,并分享了几幅精美的分形图像。此外,还提到了程序的新特性,如导入ini文件批量输出图像和更新一批图片的功能。文档末尾附有多张程序生成的高分辨率分形图像示例。
|
5月前
|
大数据 C#
C#实现多线程的几种方式
C#实现多线程的几种方式
|
5月前
|
JavaScript 前端开发 C#
初识Unity——创建代码、场景以及五个常用面板(创建C#代码、打开代码文件、场景的创建、Project、Hierarchy、Inspector、Scene、Game )
初识Unity——创建代码、场景以及五个常用面板(创建C#代码、打开代码文件、场景的创建、Project、Hierarchy、Inspector、Scene、Game )
264 0