Unity C#基础之 多线程的前世今生(上) 科普篇

多线程,项目中的应用不可缺少,能极大的提高程序的响应速度,但是也会提高内存和CPU的计算量(空间换时间),下面简单介绍下多线程从.NET 1.0版本到.NET 4.0版本的发展历程及使用示例

示例工程下载Unity 2017.3.0 P4 .NET版本4.6

在介绍之前先为大家科普下多线程的基础知识
  • 什么是进程
  • 什么是线程
  • 什么是多线程
  • 多线程的优点
  • 多线程的缺点
  • 何时使用多线程
  • 何时不要使用多线程
  • 同步和异步的区别

什么是进程?

进程是一个程序在电脑上运行时,全部计算资源的合集叫进程。当一个程序(例如QQ)开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。(QQ信息的接受和发送,图片接受发送,UI界面等)

什么是线程?

线程是程序的最小执行单位,包含计算资源,,任何一个操作的响应都是线程完成的。线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。(例如QQ的UI界面就是一个线程处理,文字的收发一个线程处理,图片的收发一个线程处理)

什么是多线程?

多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程的好处:

可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。多线程特点:不卡主线程、速度快、无序性

多线程的不利方面

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
  • 多线程需要协调和管理,所以需要CPU时间跟踪线程;
  • 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题(线程安全);
  • 线程太多会导致控制太复杂,最终可能造成很多Bug;

何时使用多线程

多线程程序一般被用来在后台执行耗时的任务。主线程保持运行,并且工作线程做它的后台工作。对于Windows Forms程序来说,如果主线程试图执行冗长的操作,键盘和鼠标的操作会变的迟钝,程序也会失去响应。由于这个原因,应该在工作线程中运行一个耗时任务时添加一个工作线程,即使在主线程上有一个有好的提示“处理中...”,以防止工作无法继续。这就避免了程序出现由操作系统提示的“没有相应”,来诱使用户强制结束程序的进程而导致错误。模式对话框还允许实现“取消”功能,允许继续接收事件,而实际的任务已被工作线程完成。BackgroundWorker恰好可以辅助完成这一功能。
在没有用户界面的程序里,比如说Windows Service, 多线程在当一个任务有潜在的耗时,因为它在等待另台电脑的响应(比如一个应用服务器,数据库服务器,或者一个客户端)的实现特别有意义。用工作线程完成任务意味着主线程可以立即做其它的事情。
另一个多线程的用途是在方法中完成一个复杂的计算工作。这个方法会在多核的电脑上运行的更快,如果工作量被多个线程分开的话(使用Environment.ProcessorCount属性来侦测处理芯片的数量)。
一个C#程序称为多线程的可以通过2种方式:明确地创建和运行多线程,或者使用.NET framework的暗中使用了多线程的特性——比如BackgroundWorker类, 线程池,threading timer,远程服务器,或Web Services或ASP.NET程序。在后面的情况,人们别无选择,必须使用多线程;一个单线程的ASP.NET web server不是太酷,即使有这样的事情;幸运的是,应用服务器中多线程是相当普遍的;唯一值得关心的是提供适当锁机制的静态变量问题。

何时不要使用多线程

多线程也同样会带来缺点,最大的问题是它使程序变的过于复杂,拥有多线程本身并不复杂,复杂是的线程的交互作用,这带来了无论是否交互是否是有意的,都会带来较长的开发周期,以及带来间歇性和非重复性的bugs。因此,要么多线程的交互设计简单一些,要么就根本不使用多线程。除非你有强烈的重写和调试欲望。
当用户频繁地分配和切换线程时,多线程会带来增加资源和CPU的开销。在某些情况下,太多的I/O操作是非常棘手的,当只有一个或两个工作线程要比有众多的线程在相同时间执行任务快的多。稍后我们将实现生产者/耗费者 队列,它提供了上述功能。

同步和异步的区别

同步方法调用在程序继续执行之前需要等待同步方法执行完毕返回结果(同一时间只能做一件事)
异步方法则在被调用之后立即返回以便程序在被调用方法完成其任务的同时执行其它操作(同一时间内可以并行做多件事)

话不多说,上Code,简单的异步多线程

    /// <summary>
    /// 异步方法
    /// </summary>
    private void AsyncOnClick()
    {
        Debug.Log($"AsyncOnClick - Start 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        Action<string> act = this.DoSomethingLong;        //act("同步菜鸟海澜");//写法等同于act.Invoke("菜鸟海澜");

        act.BeginInvoke("异步菜鸟海澜", null, null);

        Debug.Log($"AsyncOnClick - End 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    }
    /// <summary>
    /// 同步方法
    /// </summary>
    private void SyncOnClick()
    {
        Debug.Log($"SyncOnClick - Start 线程ID: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 时间点: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        this.DoSomethingLong("同步菜鸟海澜");
        Debug.Log($"SyncOnClick - End  线程ID: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 时间点:  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    }


打印结果如下

调用顺序是先调用的异步然后是同步,但是打印结果是先出现的同步结果,说明异步没有卡主线程

异步多线程是无序的:启动无序 执行时间不确定 结束无序,但是要控制他的执行顺序一般分为三类 回调 等待 状态

回调

     /// <summary>
    /// 异步回调  
    /// </summary>
    private void AsyncCallback()
    {
        #region 无返回值
        {
            Action<string> act = this.DoSomethingLong;
            IAsyncResult iAsyncResult = null;
            AsyncCallback callback = ar =>
            {
                Debug.Log(object.ReferenceEquals(ar, iAsyncResult));
                Debug.Log(ar.AsyncState);
                Debug.Log($"这里是BeginInvoke的回调{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            };
            iAsyncResult = act.BeginInvoke("AsyncOnClick01", callback, "菜鸟海澜");
        }
        #endregion
        #region 有返回值
        {
            Func<int, string> func = i => i.ToString();
            //AsyncCallback
            IAsyncResult iAsyncResult = func.BeginInvoke(DateTime.Now.Year, ar =>
            {
                string resultIn = func.EndInvoke(ar);//对于每个异步操作,只能调用一次 EndInvoke。调用EndInvoke后立即强制释放线程,否则系统在合适时机释放线程
                Debug.Log($"This is {ar.AsyncState} 的异步调用结果 {resultIn}");
            }, "回调传参菜鸟海澜");
            //string result = func.EndInvoke(iAsyncResult);//获取返回值 不在回调中调用EndInvoke卡主线程
        }
        #endregion
    }

打印结果如下

等待

    /// <summary>
    /// 异步等待
    /// </summary>
    private void AsyncWaitOnClick()
    {
        {
            Action<string> act = this.DoSomethingLong;
            IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);

            int i = 1;
            while (!iAsyncResult.IsCompleted)//1 卡界面,主线程在等待   2 边等待边做事儿  3有误差
            {
                if (i < 10)
                {
                    Debug.Log($"文件上传{i++ * 10}%。。。请等待");
                }
                else
                {
                    Debug.Log("已完成99%。。。马上结束");
                }
                Thread.Sleep(200);
            }
            Debug.Log("文件上传成功!!!");

            act.EndInvoke(iAsyncResult);//等待
            Debug.Log("这里是BeginInvoke调用完成之后才执行的。。。");
        }
    }

打印结果如下

状态

    /// <summary>
    /// 异步状态
    /// </summary>
    private void AsyncStateOnClick()
    {
        Debug.Log($"AsyncStateOnClick Start {name}  线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 时间点: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        Action<string> act = this.DoSomethingLong;
        IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);
        //iAsyncResult.AsyncWaitHandle.WaitOne();//一直等待任务完成,第一时间进入下一行
        //iAsyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成,第一时间进入下一行
        iAsyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms,否则就进入下一行,可以做一些超时控制
        Debug.Log($"AsyncStateOnClick End {name}  线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 时间点: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    }

打印结果如下

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 米斯特白帽培训讲义 工具篇 AWVS 讲师:gh0stkey 整理:飞龙 协议:CC BY-NC-SA 4.0 功...
    布客飞龙阅读 1,498评论 0 8
  • 我讨厌现在的自己。自从经历过一些事情之后,就变得颓废,每天很迷茫。 作为一个宝妈,我知道我这样是朝黄脸...
    琪琪妈咪阅读 410评论 2 3
  • 今天老师给我们大概讲解了两个小模块,温湿度传感器和语音模块,我做的是温湿度传感器,通信通过IIC来读取温湿度传感...
    葛书雨g阅读 175评论 0 0
  • ​ 还有不到 1 个月就要迎来了一年一度的春节小长假。虽然很多小仙女已经按耐不住激动的小心情,然而2月6号快递陆续...
    彩妆国季阅读 271评论 0 0
  • 【作 业】 作业一:A3-选其一,用说话三步骤来进行案例练习: 1、职场案例: 你最近在工作上遇到了一些困惑,你...
    gxuzh阅读 479评论 0 49