多线程基础
多线程的好处在于可以提高CPU的利用率——任何一个程序员都不希望自己的程序很多时候没事可干,在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
我们可以举一个例子描述多线程。假设有一个公司,公司里有很多各司其职的职员,那么我们可以认为这个正常运作的公司就是一个进程,而公司里的职员就是线程。一个公司至少得有一个职员吧,同理,一个进程至少包含一个线程。在公司里,你可以使一个职员干所有的事,但这样的话效率很显然是高不起来的,一个人的公司也不可能做大;一个程序中也可以只用一个线程去做事,就象一个人的公司一样,效率很低,如果做大程序,效率更低——事实上现在几乎没有单线程的商业软件。所以我们需要多个人同时做多件事情,但是公司的职员越多,老板就得发越多的薪水给他们,还得耗费大量精力去管理他们,协调他们之间的矛盾和利益;程序也是如此,线程越多耗费的资源也越多,需要CPU时间去跟踪线程,还得解决诸如死锁,同步等问题。总之,如果你想让你的公司做大做强,你就得多几个员工;同理,如果你不想让你的程序显得稚气,就在你的程序里引入多线程吧!
一、为什么要多线程?
我们先从一个例子出发:
static void Main(string[] args) { ShowNum(); } private static void ShowNum() { for (int i = 0; i < 500; i++) { Console.WriteLine("A"+i.ToString()); } } |
如上程序,在输出的过程中是不能再做任何事情的,必须等待他执行完成才可以进行下个任务,这就是我们所谓的“单线程”。那怎么才可以同时执行2件,甚至多件事情,而且互相不耽误?这就是我们的目标——多线程
想要同时执行多个任务,那还不简单?多写几个方法不就OK?
下来,我们改进上面的程序:
static void Main(string[] args) { ShowNum(); ShowNumToo(); } private static void ShowNum() { for (int i = 0; i < 500; i++) { Console.WriteLine("A"+i.ToString()); } } private static void ShowNumToo() { for (int i = 0; i < 500; i++) { Console.WriteLine("B" + i.ToString()); } } |
从运行的结果我们发现,并不是我们想要的,他总是把第一个方法执行完毕才会执行第二个方法,如此,如果第一个方法在执行的时候出现停顿,那么第二个方法就得一直等待,造成效率低下,资源浪费,要解决这个问题,就必须使用多线程。
二、如何多线程
使用多线程,首先得明白
static void Main(string[] args) { Thread t = new Thread(new ThreadStart(ShowNum)); t.Start(); Thread ttoo = new Thread(new ThreadStart(ShowNumToo)); ttoo.Start(); } private static void ShowNum() { for (int i = 0; i < 500; i++) { Console.WriteLine("A"+i.ToString()); } } private static void ShowNumToo() { for (int i = 0; i < 500; i++) { Console.WriteLine("B" + i.ToString()); } } |
这次我们发现在输出的时候,是2个结果交替输出的,说明了2个方法在同时运行,我们的目标基本达到。那他们的原理又是什么呢?
首先,我们在上面引入了一个命名空间,叫
using System.Threading; |
然后创建了2个线程,t和ttoo,然后给他们分别传入2个委托,表示线程开始执行时要调用的方法。这样系统就为他们分配了2个线程,他们可以独立运行。
好了,还有一些其他的操作或者属性了之类的,暂时不做详述。上面全部分析的是控制台程序,如果要转到桌面开发又该怎么处理?处理方式是否一样?
三、“线程间操作无效”的问题
如新建一个windows应用程序,在界面放置一个按钮,添加点击事件,代码如:
private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(Shows)); t.Start(); } |
再添加一个方法,实现给界面的文本框赋值:
public void Shows() { this.textBox1.Text = DateTime.Now.ToString(); } |
单击按钮,我们发现情况并不像我们想像的那样定时更新文本框的时间,而是出现异常,异常信息如:“线程间操作无效: 从不是创建控件“textBox1”的线程访问它”。原因其实很简单,控件不是由我们的线程创建,他的生命周期也不由我们的线程控制,所以.Net是不允许这样操作的,其实也是为了操作安全。
那怎么解决这个问题呢?
经过查询MSDN:对 Windows 窗体控件进行线程安全调用
只需要按照下面步骤即可:
1. 查询控件的 InvokeRequired 属性。
2. 如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。
3. 如果 InvokeRequired 返回 false,则直接调用控件。
注:
InvokeRequired表示当前线程不是创建控件的线程时为true,意思是其它线程需要访问控件,那么得声明一个委托,调用invoke来转给控件进行处理,当他的值为false时,说明访问的线程就是创建自己的线程当然可以直接操作。简单的说,如果有两个线程,Thread A和Thread B,并且有一个Control c,是在Thread A里面new的。那么在Thread A里面运行的任何方法调用c.InvokeRequired都会返回false。相反,如果在Thread B里面运行的任何方法调用c.InvokeRequired都会返回true。
Invoke的意思是在拥有此控件的基础窗口句柄的线程上执行指定的委托
delegate void SetTextCallback(string text); private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(Shows)); t.Start(); } public void Shows() { while (true) { SetText(DateTime.Now.ToString()); } } private void SetText(string text) { if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } } |
由此问题解决。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端