C#多线程基础(一) PS:阅读C#多线程编程实战第一章总结

2023-11-18

一、基本概念

进程(Process)

​ 在操作系统中正在运行的应用程序被视为一个进程,包含着一个运行程序所需要的资源,进程可以包括一个或多个线程 。

线程(Thread)

​ 进程的基本执行单元,是操作系统分配CPU时间的基本单位 ,在进程入口执行的第一个线程被视为主线程 。

​ (1)线程是一个可执行路径,它可以独立于其它线程执行。

​ (2)每个线程都在操作系统的进程(Process)内执行,而操作系统进程提供了程序运行的独立环境。

​ (3)单线程应用:在进程的独立环境里只跑一个线程,所以该线程拥有独占权。

​ (4)多线程应用:单个进程中会跑多个线程,它们会共享当前的执行环境(尤其是内存)。

线程属性

Property 描述
IsAlive 如果此线程已启动但尚未正常终止或中止,则返回 true
IsBackground 获取或设置布尔值,该值指示线程是否为后台线程。 后台线程类似前台线程,但后台线程不会阻止进程停止。 属于某个进程的所有前台线程均停止后,公共语言运行时通过对仍处于活动状态的后台进程调用 Abort 方法来结束进程。 有关详细信息,请参阅前台和后台线程
Name 获取或设置线程的名称。 最常用于在调试时查找各个线程。
Priority 获取或设置由操作系统用来确定线程计划优先顺序的 ThreadPriority 值。 有关详细信息,请参阅计划线程ThreadPriority 引用。
ThreadState 获取 ThreadState 值,该值包含线程的当前状态。

线程生命周期

线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

下面列出了线程生命周期中的各种状态:

  • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。

  • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。

  • 不可运行状态

    :下面的几种情况下线程是不可运行的:

    • 已经调用 Sleep 方法
    • 已经调用 Wait 方法
    • 通过 I/O 操作阻塞
  • 死亡状态:当线程已完成执行或已中止时的状况。

多线程的优缺点

  • 多线程优点:可以同时完成多个任务;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。

  • 多线程的缺点:

    (1) 内存占用 线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多(每个线程都需要开辟堆栈空间,多线程时有时需要切换时间片)。

    (2) 管理协调 多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程,线程太多会导致控制太复杂。

    (3)资源共享 线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。

二、编程实战

1、创建多线程

(1)为主线程命名为"主线程",并创建一个名为“子线程”的线程。
(2)使用.Start()方法开启子线程。
(3)在主、子线程中分别执行PrintNumbers()方法。

        static void Main(string[] args)
        {
            //设置主线程名
            Thread.CurrentThread.Name = "主线程";

            //开启子线程,并设置名
            Thread thread = new Thread(PrintNumbers);
            thread.Name = "子线程";
            thread.Start();
            //主线程中调用PrintNumbers()方法
            PrintNumbers();
            Console.ReadKey();
        }
        /// <summary>
        /// 输出数字
        /// </summary>
        static void PrintNumbers()
        {
            var threadName = Thread.CurrentThread.Name;
            Console.WriteLine("当前线程:" + threadName);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(threadName + ":" + i);
            }
        }

(4)可看到主、子线程交替打印0-9之间的数字(注:不同的电脑打印出来的顺序不一样,可多次执行程序,观察异同),如下图:
在这里插入图片描述

2、线程暂停

(1)PrintNumbers()方法表示正常输出0-9之间的数字,PrintNumbersWithDelay()方法表示每次输出数字前都让当前线程先“睡(暂停)”1秒,然后再输出数字。
(2) Thread.Sleep()是用来执行线程暂停的方法,在主线程中,则是暂停主线程,子线程中则暂停子线程。
(3)在主线程中调用PrintNumbers()方法正常输出,子线程中调用PrintNumbersWithDelay()方法延迟输出。

        static void Main(string[] args)
        {
            //设置主线程名
            Thread.CurrentThread.Name = "主线程";

            //开启子线程,并设置名
            Thread thread = new Thread(PrintNumbersWithDelay);
            thread.Name = "子线程";
            thread.Start();
            //主线程中调用PrintNumbers()方法
            PrintNumbers();
            Console.ReadKey();
        }
        /// <summary>
        /// 输出数字
        /// </summary>
        static void PrintNumbers()
        {
            var threadName = Thread.CurrentThread.Name;
            Console.WriteLine("当前线程:" + threadName);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(threadName + ":" + i);
            }
        }

        /// <summary>
        /// 延迟输出数字
        /// </summary>
        static void PrintNumbersWithDelay()
        {
            var threadName = Thread.CurrentThread.Name;
            Console.WriteLine("当前线程:" + threadName);
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));//每次都暂停1秒再执行输出操作
                Console.WriteLine(threadName + ":" + i);
            }
        }

(4)结果如下图所示:
在这里插入图片描述

3、线程等待

(1)PrintNumbersWithDelay()方法表示每次输出数字前都让当前线程先“睡(暂停)”1秒,然后再输出数字。
(2)使用线程的Join()方法,让主线程等待子线程执行完。

static void Main(string[] args)
        {
            //设置主线程名
            Thread.CurrentThread.Name = "主线程";

            //开启子线程,并设置名
            Thread thread = new Thread(PrintNumbersWithDelay);
            thread.Name = "子线程";
            thread.Start();
            thread.Join();//线程等待,等待子线程执行完后,才会继续往下执行
            Console.WriteLine(Thread.CurrentThread.Name+"执行结束。。。");
            Console.ReadKey();
        }
        /// <summary>
        /// 延迟输出数字
        /// </summary>
        static void PrintNumbersWithDelay()
        {
            var threadName = Thread.CurrentThread.Name;
            Console.WriteLine("当前线程:" + threadName);
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));//每次都暂停四秒再执行输出操作
                Console.WriteLine(threadName + ":" + i);
            }
        }

(3)如下图,当子线程输出完才输出“主线程执行结束。。。”,如果将join去掉,将会出现先输出“主线程执行结束。。。”再输出子线程中的数字。
在这里插入图片描述

4、线程终止

(1)创建子线程调用方法循环打印数字。
(2)让主线程等待6秒后,调用Abort()方法让线程终止。

static void Main(string[] args)
        {
            Console.WriteLine("Starting program...");
            Thread t = new Thread(PrintNumbersWithDelay);
            t.Start();
            Thread.Sleep(TimeSpan.FromSeconds(6));
            t.Abort();
            Console.WriteLine("A thread has been aborted");
        }

        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("Starting...");
            for (int i = 1; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }

(3)可看到如下图所示的异常,提示当前平台不支持使用Abort(net core3.0),故不支持使用Abort()方法来终止线程,后续会介绍CancellationToken来取消线程的执行。
在这里插入图片描述

5、检测线程状态

(1)使用线程的ThreadState打印当前线程的状态,ThreadState是一个枚举值。
(2)创建一个子线程,并在子线程中使用 Thread.Sleep(TimeSpan.FromSeconds(1))让每次打印都先暂停一秒。
(3)主线程中循环十次去打印子线程的状态。
(4)主线程中暂停5秒后,调用Join()方法,让主线程等待子线程打印完成后再继续执行。

//ThreadState枚举值如下:
//Running = 0,
//StopRequested = 1,
//SuspendRequested = 2,
//Background = 4,
//Unstarted = 8,
//Stopped = 16,
//WaitSleepJoin = 32,
//Suspended = 64,
//AbortRequested = 128,
//Aborted = 256
 static void Main(string[] args)
        {
            //设置主线程名
            Thread.CurrentThread.Name = "主线程";
            Console.WriteLine(Thread.CurrentThread.Name + ":" + Thread.CurrentThread.ThreadState.ToString());
            Thread t = new Thread(PrintNumbersWithStatus);
            t.Name = "子线程";
            t.Start();
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("主线程中打印子线程状态,当前子线程:{0},其状态为:{1}", t.Name, t.ThreadState.ToString());
            }
            Thread.Sleep(TimeSpan.FromSeconds(5));
            t.Join();
            Console.WriteLine(t.Name + ":" + t.ThreadState.ToString());
            Console.WriteLine("执行结束。。。");
            Console.ReadKey();
        }
        /// <summary>
        /// 
        /// </summary>
        static void PrintNumbersWithStatus()
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                Console.WriteLine(Thread.CurrentThread.Name + ":" + i);
            }

        }

(5)如下图,可看到子线程的状态变化为"Running"->“WaitSleepJosin”->“Stopped”
在这里插入图片描述

6、线程优先级

(1)创建ThreadSample类,其中包含CountNumbers()、stop()方法以及counter和_isStopped字段。
(2)在不限定CPU核数的情况下,设置线程一为高优先级,线程二为低优先级,可看到优先级高的在两秒内计算的次数比优先级低的多了几百万。
(3)使用“ Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1)”限定CPU核数为1核后,优先级低的线程无法进行任何计算操作,因其资源都被线程高的掠夺去了。
(4)此例子可看出优先级高的线程拥有的计算机资源会更多!

//线程优先级枚举值如下:
//Lowest = 0,
//BelowNormal = 1,
//Normal = 2,
//AboveNormal = 3,
//Highest = 4
        static void Main(string[] args)
        {
            Console.WriteLine("当前线程优先级(priority): {0}", Thread.CurrentThread.Priority);
            Console.WriteLine("不限定计算机处理器核数进行计算与输出!");
            RunThreads();//不限定处理器核数,并开启线程
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("设置当前为单核处理器并进行计算与输出!");
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
            RunThreads();//限定为单核处理器并进行计算与输出!
        }
        /// <summary>
        /// 开启两个线程,并调用计数器
        /// </summary>
        static void RunThreads()
        {
            var sample = new ThreadSample();

            var threadOne = new Thread(sample.CountNumbers);
            threadOne.Name = "线程一";
            var threadTwo = new Thread(sample.CountNumbers);
            threadTwo.Name = "线程二";

            //设置线程一优先级级别为最高
            threadOne.Priority = ThreadPriority.Highest;
            //设置线程二优先级级别为最低
            threadTwo.Priority = ThreadPriority.Lowest;
            threadOne.Start();
            threadTwo.Start();
            //线程暂停两秒
            Thread.Sleep(TimeSpan.FromSeconds(2));
            //停止子线程的计算操作
            sample.Stop();
        }
    /// <summary>
    /// 线程优先级
    /// </summary>
    class ThreadSample
    {
        //定义一个暂停标识
        private bool _isStopped = false;

        //设置暂停的方法
        public void Stop()
        {
            _isStopped = true;
        }
        /// <summary>
        /// 数值统计并输出当前线程名与线程优先级
        /// </summary>
        public void CountNumbers()
        {
            //计数器
            long counter = 0;

            
            while (!_isStopped)
            {
                counter++;
            }
            //输出当前线程名+线程优先级+计数器最终结果
            Console.WriteLine("{0} with {1,11} priority " +
                        "has a count = {2,13}", Thread.CurrentThread.Name,
                        Thread.CurrentThread.Priority,
                        counter.ToString());
        }



    }

(5)执行结果如下:
在这里插入图片描述
以上代码及思路参考的是“多线程编程实战第一章”。
源码:https://github.com/xgysigned/C-Sharp-Thread
续篇:C#多线程基础(二) PS:阅读C#多线程编程实战第一章总结
持续更新中。。。。。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#多线程基础(一) PS:阅读C#多线程编程实战第一章总结 的相关文章

  • 无法使用 strptime() 获取秒数

    我收到 YYYY MM DDThh mm ss S Z hh mm 这种格式的日期时间 我正在尝试使用复制该值strptime如下所示 struct tm time 0 char pEnd strptime datetime Y m dT
  • UTF8/UTF16 和 Base64 在编码方面有什么区别

    In c 我们可以使用下面的类来进行编码 System Text Encoding UTF8 System Text Encoding UTF16 System Text Encoding ASCII 为什么没有System Text En
  • 如何在 Unity 中从 RenderTexture 访问原始数据

    问题的简短版本 我正在尝试访问 Unity 中 RenderTexture 的内容 我一直在使用 Graphics Blit 使用自己的材质进行绘制 Graphics Blit null renderTexture material 我的材
  • 如何在C++中实现模板类协变?

    是否可以以这样一种方式实现类模板 如果模板参数相关 一个对象可以转换为另一个对象 这是一个展示这个想法的例子 当然它不会编译 struct Base struct Derived Base template
  • 如何在没有 Control.Invoke() 的情况下从后台线程修改控件属性

    最近 我们遇到了一些旧版 WinForms 应用程序 我们需要更新一些新功能 在专家测试该应用程序时 发现一些旧功能被破坏 无效的跨线程操作 现在 在您认为我是新手之前 我确实有一些 Windows 窗体应用程序的经验 我不是专家 但我认为
  • fgets() 和 Ctrl+D,三次才能结束?

    I don t understand why I need press Ctrl D for three times to send the EOF In addition if I press Enter then it only too
  • C# 中可空类型是什么?

    当我们必须使用nullable输入 C net 任何人都可以举例说明 可空类型 何时使用可空类型 https web archive org web http broadcast oreilly com 2010 11 understand
  • 如何针对 Nancy 中的 Active Directory 进行身份验证?

    这是一篇过时的文章 但是http msdn microsoft com en us library ff650308 aspx paght000026 step3 http msdn microsoft com en us library
  • 为什么模板不能位于外部“C”块内?

    这是一个后续问题一个答案 https stackoverflow com questions 4866433 is it possible to typedef a pointer to extern c function type wit
  • Windows 窗体不会在调试模式下显示

    我最近升级到 VS 2012 我有一组在 VS 2010 中编码的 UI 测试 我试图在 VS 2012 中启动它们 我有一个 Windows 窗体 在开始时显示使用 AssemblyInitialize 属性运行测试 我使用此表单允许用户
  • 如何在 Team Foundation 上强制发表有意义的签入评论?

    我有一个开发团队有一个坏习惯 他们写道poor签入评论 当我们必须在团队基础上查看文件的历史记录时 这使得它成为一场噩梦 我已经启用了变更集评论政策 这样他们甚至可以在签到时留下评论 否则他们不会 我们就团队的工作质量进行了一些讨论 他们很
  • 初始化变量的不同方式

    在 C 中初始化变量有多种方法 int z 3 与 int 相同z 3 Is int z z 3 same as int z z 3 您可以使用 int z z 3 Or just int z 3 Or int z 3 Or int z i
  • Windows 10 中 Qt 桌面应用程序的缩放不当

    我正在为 Windows 10 编写一个简单的 Qt Widgets Gui 应用程序 我使用的是 Qt 5 6 0 beta 版本 我遇到的问题是它根本无法缩放到我的 Surfacebook 的屏幕上 这有点难以判断 因为 SO 缩放了图
  • 可空属性与可空局部变量

    我对以下行为感到困惑Nullable types class TestClass public int value 0 TestClass test new TestClass Now Nullable GetUnderlyingType
  • 在 URL 中发送之前对特殊字符进行百分比编码

    我需要传递特殊字符 如 等 Facebook Twitter 和此类社交网站的 URL 为此 我将这些字符替换为 URL 转义码 return valToEncode Replace 21 Replace 23 Replace 24 Rep
  • 已过时 - OpenCV 的错误模式

    我正在使用 OpenCV 1 进行一些图像处理 并且对 cvSetErrMode 函数 它是 CxCore 的一部分 感到困惑 OpenCV 具有三种错误模式 叶 调用错误处理程序后 程序终止 Parent 程序没有终止 但错误处理程序被调
  • 在 ASP.NET 中将事件冒泡为父级

    我已经说过 ASP NET 中的层次结构 page user control 1 user control 2 control 3 我想要做的是 当控件 3 它可以是任何类型的控件 我一般都想这样做 让用户用它做一些触发回发的事情时 它会向
  • 如何使用 ReactiveList 以便在添加新项目时更新 UI

    我正在创建一个带有列表的 Xamarin Forms 应用程序 itemSource 是一个reactiveList 但是 向列表添加新项目不会更新 UI 这样做的正确方法是什么 列表定义 listView new ListView var
  • 将 viewbag 从操作控制器传递到部分视图

    我有一个带有部分视图的 mvc 视图 控制器中有一个 ActionResult 方法 它将返回 PartialView 因此 我需要将 ViewBag 数据从 ActionResult 方法传递到 Partial View 这是我的控制器
  • 不同类型的指针可以互相分配吗?

    考虑到 T1 p1 T2 p2 我们可以将 p1 分配给 p2 或反之亦然吗 如果是这样 是否可以不使用强制转换来完成 或者我们必须使用强制转换 首先 让我们考虑不进行强制转换的分配 C 2018 6 5 16 1 1 列出了简单赋值的约束

随机推荐