这和前面的学习内容可能有点不太连贯,但是呢我们一般来说的学习就是遇到什么困难就去学习什么,这也是为什么看那些循序渐进的教程虽然学的很饱满,但是我们有时会学了前面忘记了后面,或者对某个板块理解不深,乃至于写代码虽然能看懂ai的解释,却不能融会贯通。
就好像现在:我学习网络通信,却遇到了关于同步异步的问题,虽然本科时有粗略学习过一系列多线程之类的概念,但是在具体问题中发现远远不够,十分迷糊。于是决定学习这一章。
线程是什么?
每个操作系统上运行的应用程序都是一个进程,一个进程可以包括一个或多个线程。
线程是操作系统分配处理器时间的基本单元。
在进程中可以有多个线程。
线程上下文
线程上下文是指保证线程在宿主进程地址空间中无缝继续运行所必须得所有信息。
单线程
只有一个线程,从头执行到尾的程序叫做单线程程序,默认情况下系统会给应用程序分配一个入口也就是常见的Main方法。如果你新建一个winform程序,你可以搜索Main方法看看它创建了什么。
internal static class Program{/// <summary>/// 应用程序的主入口点。/// </summary>[STAThread]static void Main(){Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new Form1());}}
怎么样?一个窗体界面运行在一个线程,于是我们在其他线程中,比如网络操作,文件读写的线程中如果修改这里的窗体界面,会报一个很常见很常见的错误哦。
当然,这个我们后面再说。
什么是多线程?
很好,我们刚刚说过了,用户界面在一个线程上运行,我们又知道,线程是分配处理器时间的最小单元,那么如果我们只有一个线程,如果遇到一个任务需要很长时间,就只能等待这个事情做完了,相应的,我们的用户界面也会卡住了,这是古早一些软件的常见情况,点击一个按钮触发了事件之后,很长一段时间都没有反应。。。
于是我们就尝试使用多个线程来运行我们的程序,遇到需要占用时间的事情我们就新开一个线程,然后给他扔到我们的后台去。这样我们前台的用户界面就不会卡顿,还能继续处理用户的其他操作,这可比原来强多了。
多线程的常用场景
举几个例子:
网络通信服务,毫无疑问咯,网络的延迟是很不稳定的因素,乃至于传输文件,都是既耗时又占用资源的操作,其中有很多需要线程间的交互。这也是我们上一节为什么没有继续学习的原因。
数据库操作,这个就不需要我们多说了,用户界面中展示的数据来自于数据库的情况实在是太常见了,一般来说我们会提供用户操作数据库的几个方法,但是这些操作数据库的方法也许会比较耗时,比如大批量复制和排序,乃至于与网络通信结合,与云数据库通信。
多线程的限制
当然不是说:多线程实在是太厉害了,我们一定要多使用多线程,这样我们就能同时运行好多任务啦!实际上不是这样的,多线程优点很多,缺点也是存在的,
首先是占用内存,其次是太多线程会占用大量的处理器时间,会导致线程之间互相影响,都不会有太多的进度。
对于程序员来说,线程太多管理起来也是麻烦,还有可能产生许多bug。
线程的实现
Thread类
此类属于System.Threading命名空间下。
Thread 类 (System.Threading) | Microsoft Learn
属性:
ApartmentState
已过时.
获取或设置此线程的单元状态。CurrentCulture
获取或设置当前线程的区域性。CurrentPrincipal
获取或设置线程的当前负责人(对基于角色的安全性而言)。CurrentThread
获取当前正在运行的线程。CurrentUICulture
获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。ExecutionContext
获取 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。IsAlive
获取指示当前线程的执行状态的值。IsBackground
获取或设置一个值,该值指示某个线程是否为后台线程。IsThreadPoolThread
获取指示线程是否属于托管线程池的值。ManagedThreadId
获取当前托管线程的唯一标识符。Name
获取或设置线程的名称。Priority
获取或设置指示线程的调度优先级的值。ThreadState
获取一个值,该值包含当前线程的状态。
创建线程
构造函数
创建线程的构造函数有好几个,但是无一例外需要一个参数:委托,事实上就是告诉线程执行什么。其中传递的委托可以是ThreadStart的实例。
Thread(ParameterizedThreadStart)
初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托。Thread(ParameterizedThreadStart, Int32)
初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托,并指定线程的最大堆栈大小。Thread(ThreadStart)
初始化 Thread 类的新实例。Thread(ThreadStart, Int32)
初始化 Thread 类的新实例,指定线程的最大堆栈大小。
举例:
下面就是初始化一个线程,然后启用它的例子。
public void serverstart()
{//开启服务端线程mywaitthread = new Thread(new ThreadStart(serverrun));mywaitthread.Start();tm_checkmessage1.Start();}
释放线程
一般来说,我们创建的线程和系统自动给的线程一样,在执行完任务之后会自动释放。
但是如果线程具有循环的特性就需要我们给一个跳出循环的点。
或者是线程执行时间太长,我们需要手动释放。此时使用:
if (myclientthread != null)
{if (myclientthread.ThreadState == ThreadState.Running){myclientthread.Abort();}
}
Abort方法可以引发结束线程的过程。一般来说这会导致线程的结束。
如此我们就知道怎么使用一个线程来执行我们预设的方法了,但是这是最简单的,也可以说是最好理解的多线程或者说不同于单线程的逻辑。
我们只是把我们程序从一条线,在某个节点,引出一条另外的线罢了。线程之间的交互,线程的状态我们都没有去详细解释。
明天会试图弄懂更多的程序执行的流程逻辑,尤其是:异步,同步这两个概念在很多面试官眼中的地位可以说很高很高的东西。