详解C#多线程编程之进程与线程
一、 进程
简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间。一个程序想要并发执行,开多个进程即可。
Q1:在单核下,进程之间如何同时执行?
首先要区分两个概念——并发和并行
- 并发:并发是指在一段微小的时间段中,有多个程序代码段被CPU执行,宏观上表现出来就是多个程序能”同时“执行。
- 并行:并行是指在一个时间点,有多个程序段代码被CPU执行,它才是真正的同时执行。
所以应该说进程之间是并发执行。对于CPU来讲,它不知道进程的存在,CPU主要与寄存器打交道。有一些常用的寄存器,如程序计数器寄存器,这个寄存器存储了将要执行的指令的地址,这个寄存器的地址指向哪,CPU就去哪。还有一些堆栈寄存器和通用寄存器等等等,总之,这些数据构成了一个程序的执行环境,这个执行环境就叫做”上下文(Context)“,进程之间切换本质就是保存这些数据到内存,术语叫做”保存现场“,然后恢复某个进程的执行环境,也即是”恢复现场“,整个过程术语叫做“上下文切换”,具体点就是进程上下文切换,这就是进程之间能并发执行的本质——频繁的切换进程上下文。这个功能是由操作系统提供的,是内核态的,对应用软件开发人员透明。
二、 线程
进程虽然支持并发,但是对并发不是很友好,不友好是指每开启一个进程,都要重新分配一部分资源,而线程相对进程来说,创建线程的代价比创建进程要小,所以引入线程能更好的提高并发性。在现代操作系统中,进程变成了资源分配的基本单位,而线程变成了执行的基本单位,每个线程都有独立的堆栈空间,同一个进程的所有线程共享代码段和地址空间等共享资源。相应的上下文切换从进程上下文切换变成了线程上下文切换。
三、 为什么要引入进程和线程#
- 提高CPU利用率,在早期的单道批处理系统中,如果执行中的代码需要依赖与外部条件,将会导致CPU空闲,例如文件读取,等待键盘信号输入,这将浪费大量的CPU时间。引入多进程和线程可以解决CPU利用率低这个问题。
- 隔离程序之间的数据(每个进程都有单独的地址空间),保证系统运行的稳定性。
- 提高系统的响应性和交互能力。
四、 在C#中创建托管线程
1. Thread类
在.NET中,托管线程分为:
- 前台线程
- 后台线程
一个.Net程序中,至少要有一个前台线程,所有前台线程结束了,所有的后台线程将会被公共语言运行时(CLR)强制销毁,程序执行结束。
如下将在控制台程序中创建一个后台线程
static void Main(string[] args) { var t = new Thread(() => { Thread.Sleep(1000); Console.WriteLine("执行完毕"); }); t.IsBackground = true; t.Start(); }
主线程(默认是前台线程)执行完毕,程序直接退出。
2. 有什么问题
直接使用Thread类来进行多线程编程浪费资源(服务器端更加明显)且不方便,举个栗子。
假如我写一个Web服务器程序,每个请求创建一个线程,那么每一次我都要new一个Thread对象,然后传入处理HttpRequest的委托,处理完之后,线程将会被销毁,这将会导致浪费大量CPU时间和内存,在早期CPU性能不行和内存资源珍贵的情况下这个缺点会被放大,在现在这个缺点不是很明显,原因是硬件上来了。
不方便体现在哪呢?
- 无法直接获取另一个线程内未被捕捉的异常
- 无法直接获取线程函数的返回值
public static void ThrowException() { throw new Exception("发生异常"); } static void Main(string[] args) { var t = new Thread(() => { Thread.Sleep(1000); ThrowException(); }); t.IsBackground = false; try { t.Start(); } catch(Exception e) { Console.WriteLine(e.Message); } }
上述代码将会导致程序奔溃,如下图。
要想直接获取返回值和可以直接从主线程捕捉线程函数内未捕捉的异常,我们可以这么做。
新建一个MyTask.cs文件,内容如下
using System; using System.Threading; namespace ConsoleApp1 { public class MyTask { private Thread _thread; private Action _action; private Exception _innerException; public MyTask() { } public MyTask(Action action) { _action = action; } protected virtual void Excute() { try { _action(); } catch(Exception e) { _innerException = e; } } public void Start() { if (_thread != null) throw new InvalidOperationException("任务已经开始"); _thread = new Thread(() => Excute()); _thread.Start(); } public void Start(Action action) { _action = action; if (_thread != null) throw new InvalidOperationException("任务已经开始"); _thread = new Thread(() => Excute()); _thread.Start(); } public void Wait() { _thread.Join(); if (_innerException != null) throw _innerException; } } public class MyTask<T> : MyTask { private Func<T> _func { get; } private T _result; public T Result { private set => _result = value; get { base.Wait(); return _result; } } public MyTask(Func<T> func) { _func = func; } public new void Start() { base.Start(() => { Result = _func(); }); } } }
简单的包装了一下(不要在意细节),我们便可以实现我们想要的效果。
测试代码如下
public static void ThrowException() { throw new Exception("发生异常"); } public static void Test3() { MyTask<string> myTask = new MyTask<string>(() => { Thread.Sleep(1000); return "执行完毕"; }); myTask.Start(); try { Console.WriteLine(myTask.Result); } catch (Exception e) { Console.WriteLine(e.Message); } } public static void Test2() { MyTask<string> myTask = new MyTask<string>(() => { Thread.Sleep(1000); ThrowException(); return "执行完毕"; }); myTask.Start(); try { Console.WriteLine(myTask.Result); } catch(Exception e) { Console.WriteLine(e.Message); } } public static void Test1() { MyTask myTask = new MyTask(() => { Thread.Sleep(1000); ThrowException(); }); myTask.Start(); try { myTask.Wait(); } catch (Exception e) { Console.WriteLine(e.Message); } } static void Main(string[] args) { Test1(); Test2(); Test3(); }
可以看到,我们可以通过简单包装Thread对象,便可实现如下效果
- 直接读取线程函数返回值
- 直接捕捉线程函数未捕捉的异常(前提是调用了Wait()函数或者Result属性)
这是理解和运用Task的基础,Task功能非常完善,但是运用好Task需要掌握许多概念,下篇文章再说。
以上就是详解C#多线程编程之进程与线程的详细内容,更多关于C#多线程编程 进程与线程的资料请关注猪先飞其它相关文章!
相关文章
C# WinForm多线程解决界面卡死问题的完美解决方案,使用BeginInvoke
问题描述:当我们的界面需要在程序运行中不断更新数据时,当一个textbox的数据需要变化时,为了让程序执行中不出现界面卡死的现像,最好的方法就是多线程来解决一个主线程来创建界...2020-06-24- 这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
- 我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
- 这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
- 这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
- 本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
- 本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)
这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25- 这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
- 这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
- 最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
- 这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
- 这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
- 下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
- 这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25