【C#多线程】1.Thread类的使用及注意要点
Thread随便讲讲
因为在C#中,Thread类在我们的新业务上并不常用了(因为创建一个新线程要比直接从线程池拿线程更加耗费资源),并且在.NET4.0后新增了Task类即Async与await关键字,使得我们基本不再用Thread了,不过在学习多线程前,有必要先了解下Thread类,这里就先随便讲讲Thread。
1.使用多线程的几种方式
多线程Thread类只支持运行两种方法,一种是无参数并且无返回值的方法,第二种是有一个Object类型参数(有且只能有一个参数,并且必须是Object类型)且无返回值的方法。如果想让多线程方法携带多个参数,可以将多个参数放入一个集合或数组中传入方法。
下面例子使用了控制台来演示多线程的简单使用:
using System;
using System.Threading; namespace ConsoleApplication1
{
class Program
{
//无参数无返回值方法
public static void DoSomething()
{
for (int i = ; i < ; i++)
{
Thread.Sleep();
}
}
//有参数无返回值方法
public static void DoSomethingWithParameter(object obj)
{
for (int i = ; i < (int)obj; i++)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();
}
} static void Main(string[] args)
{
//获取主线程ID
int currentThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"---------主线程<{currentThreadId}>开始运行---------");
//多线程运行无参数方法方式1
ThreadStart ts = DoSomething;//ThreadStart是一个无参数,无返回值的委托
Thread thread1 = new Thread(ts);
thread1.Start(); //多线程运行无参数方法方式2
Thread thread2 = new Thread(DoSomething);//可省略ThreadStart
thread2.Start(); //多线程运行有参数方法方式1
//ParameterizedThreadStart是一个有一个Object类型参数,但是无返回值的委托。
ParameterizedThreadStart pts = DoSomethingWithParameter;
Thread thread3 = new Thread(pts);
thread3.Start(); //多线程运行有参数方法方式2
//可以省略ParameterizedThreadStart
Thread thread4 = new Thread(DoSomethingWithParameter);
thread4.Start(); //还可以使用lamda表达式简化多线程写法
new Thread(() =>
{
for (int i = ; i < ; i++)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();
}
}).Start();
Console.WriteLine($"---------主线程<{currentThreadId}>运行结束---------");
}
}
}
运行结果如下:

2.前台线程与后台线程
- 前台线程
如主线程(或称为UI线程)就是前台线程,默认Thread的实例均为前台线程,前台线程的特点是,如果当前应用的前台线程没有全部运行完毕,那么当前应用就无法退出。举个例子,我们知道正常情况下,控制台应用在Main方法结束后会自动结束当前进程,如果我们在Main方法中创建了一个新Thread线程,并使其保持运行,那么即使Main方法执行完毕,控制台进程也无法自动关闭(除非手动右上角点×)。就如下图情况,画红圈的地方表示Main方法执行完毕,可是程序依旧在运行,所以我们一般在用Thread的时候会将Thread设置为后台线程。

- 后台线程
后台线程与前台线程的唯一区别是,它不会去影响程序的生老病死,当程序的前台线程全部关闭(即程序退出),那么即使程序的后台线程依旧在执行任务,那么也会强制关闭。
设置Thread为后台线程的方式:
Thread tt = new Thread(DoSomething);
tt.IsBackground = true;//设置tt为后台线程
tt.Start();
前台线程与后台线程对程序的影响效果看似好像不算大,但是如果我们在做Winform或者WPF项目时,若在某窗体内执行一个新线程任务(这个新线程是前台线程),如果在任务执行期间关闭程序,此时会发现,虽然界面都被关闭,但是计算机任务管理器中此程序依旧还在运行(并且如果在新线程中执行的任务异常导致线程无法关闭,那么这个程序就会一直在后台跑下去),再次开启程序可能会导致打不开等后果,这种行为是非常不好的。所以我们一般使用多线程Thread类时,最好顺手将它设置为后台线程。我们可以举个例子。
static void Main(string[] args)
{
//获取主线程ID
int currentThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"---------主线程<{currentThreadId}>开始运行---------"); //执行一个大概可以运行50秒的新线程
Thread t = new Thread(() =>
{
for (int i = ; i < ; i++)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();
}
});
t.IsBackground = true;//设置t为后台线程
t.Start(); Console.WriteLine($"---------主线程<{currentThreadId}>运行结束---------");
}
这个例子的运行结果就不截图了,因为控制台会一闪而过(立即执行完Main方法便关闭),即使后台线程t还在执行任务,但是也会强制关闭。
3.让主线程等待新线程执行完成后再继续执行(使用Thread的Join方法)
直接上代码:
static void Main(string[] args)
{
//获取主线程ID
int currentThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"---------主线程<{currentThreadId}>开始运行---------"); //执行一个大概可以运行50秒的新线程
Thread t = new Thread(() =>
{
for (int i = ; i < ; i++)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();
}
});
t.IsBackground = true;//设置t为后台线程
t.Start(); t.Join();//在t线程执行期间,如果主线程调用t线程的Join方法,主线程会卡在这个地方直到t线程执行完毕 Console.WriteLine($"---------主线程<{currentThreadId}>运行结束---------");
}
4.Thread实例的其他常用方法
直接看代码注释吧:
static void Main(string[] args)
{
//执行一个大概可以运行50秒的新线程
Thread t = new Thread(DoSth);
t.IsBackground = true;//设置t为后台线程
t.Start(); t.Join();//在t线程执行期间,如果主线程调用t线程的Join方法,主线程会卡在这个地方知道t线程执行完毕
t.Priority = ThreadPriority.Normal;//设置线程调度的优先级
ThreadState rhreadState = t.ThreadState;//获取线程运行状态。
bool b = t.IsAlive;//获取线程当前是否存活
t.Interrupt();//中断当前线程
t.Abort();//终止线程
}
5.Thread类的常用方法
直接看代码注释吧:
static void Main(string[] args)
{
//使得当前线程暂停1秒再继续执行,此处会暂停主线程1秒钟
//如果写在其他线程执行的方法中,会让执行那个方法的线程暂停1秒再继续执行)
Thread.Sleep(); //获取当前执行线程的线程实例
Thread t = Thread.CurrentThread;
}
6.使用多线程需要注意的要点
(1)子线程不可以直接调用UI线程(即主线程)的UI对象,但是可以调用在主线程自定义的对象
我们在做Winform或WPF开发时,例如在前端有一个TextBox文本框,其Name属性为textBox,那么如果我们在此窗体内开启了个子线程,并在子线程内对textBox.Text赋值,是会报错的,因为子线程无法访问主线程的UI元素(实质是UI元素必须由创建它的线程去操作)。
如下代码,子线程操作主线程创建的对象时不会报错,但是子线程操作主线程创建的UI对象时会报错:
private void button1_Click(object sender, EventArgs e)
{
Student stu = new Student();//主线程创建的Student类实例
new Thread(() =>
{
stu.Name = "ccc";//子线程操作主线程创建的对象并不会报错。
textBox1.Text = "abc";//子线程直接调用UI线程textBox1会报错
}).Start();
}

解决思路:在子线程想操作UI线程的UI元素时,呼叫主线程去操作即可,代码如下:
delegate void DoSth(string str);//创建一个委托
public void SetTextBox(string str)//创建一个委托方法用于改变主线程textBox的值
{
textBox1.Text = str;
}
//按钮点击事件
private void button1_Click(object sender, EventArgs e)
{
//在子线程内执行....
new Thread(() =>
{
//----------------------详细写法------------------------
DoSth delegateMethod = new DoSth(SetTextBox);//创建方法的委托
//this指当前Window
//this.Invoke指让创建窗体的线程执行某委托方法
//第二个参数是传入委托方法即SetTextBox的参数
this.Invoke(delegateMethod, "abc"); //----------------------简写方式----------------------
this.Invoke(new Action(() =>
{
textBox1.Text = "abc";//子线程直接调用UI线程textBox1会报错
}));
}).Start();
补充:上面代码是Winform跨线程操作UI元素的常用方式,那么WPF怎么跨线程操作UI呢?直接看下面代码吧
//方式1(常用):获取当前应用的UI线程,执行某方法
App.Current.Dispatcher.Invoke(() =>
{
textBox1.Text="abc"
}); //方式2(只能在this是当前Window或可以获取到窗体实例的情况下使用):
this.Dispatcher.Invoke(new Action(()=>
{
textBox1.Text="abc"
}));
(2)多线程同时访问一个资源时,要注意同步问题。
比如两个及以上的线程同时访问一个资源(可以是文件,可以是对象),如果没有注意同步问题,会导致以下问题。直接看代码
private void button1_Click(object sender, EventArgs e)
{
int num = ;
//创建两个线程对num进行累加,各加100000,理论上线程执行完毕后最后的值应该是200000
Thread t1 = new Thread(() =>
{
for (int i = ; i < ; i++)
{
num++;
}
});
Thread t2 = new Thread(() =>
{
for (int i = ; i < ; i++)
{
num++;
}
});
//两个子线程同时执行
t1.Start();
t2.Start();
//等待t1与t2线程执行完毕再继续执行
t1.Join();
t2.Join();
Console.WriteLine(num.ToString());//输出num的值
}
结果:
结果并不是200000,因为t1与t2线程同时对num进行自增操作时候,经常会出现t1读取到了num为99,自增1,结果100赋值给num,但是在t1刚读取到num值并且还没进行自增操作时,t2也读取到了num为99,自增下也是100赋值给num。也就是说t1与t2进行了相同的操作。

如何避免当前这个问题呢?就需要在多线程访问一个资源时,进行资源同步处理。那什么是同步呢?同步是指我用完了你才能用,你我不能同时使用一个资源。这个问题的详细解决方法会在该系列以后的博客中写。
下节我们会简单讲讲线程池+以前的两种异步处理机制。
【C#多线程】1.Thread类的使用及注意要点的更多相关文章
- (转)多线程——继承Thread 类和实现Runnable 接口的区别
java中我们想要实现多线程常用的有两种方法,继承Thread 类和实现Runnable 接口,有经验的程序员都会选择实现Runnable接口 ,其主要原因有以下两点: 首先,java只能单继承,因此 ...
- 多线程:Thread类的Join()方法
多线程:Thread类的Join()方法 http://blog.163.com/hc_ranxu/blog/static/3672318220095284513678/ 当我们在线程B中调用Thre ...
- 多线程之 Thread类
一.多线程第一种方式的实现步骤(继承Thread类) 代码演示: 1.定义MyThread类,继承Thread类 2.重写了里面的run方法,在run方法中定义线程要执行的任务 public clas ...
- Java多线程01(Thread类、线程创建、线程池)
Java多线程(Thread类.线程创建.线程池) 第一章 多线程 1.1 多线程介绍 1.1.1 基本概念 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于 ...
- Java中实现多线程继承Thread类与实现Runnable接口的区别
Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 在实际应用中, ...
- 多线程——继承Thread类实现一个多线程
继承Thread类实现一个多线程 Thread类部分源码: package java.lang; //该类实现了Runnable接口 public class Thread implements Ru ...
- Java 多线程 (Thread 类)
1.多线程 2.卖票 1.多线程实现 两种方式可以实现多线程: 继承 Thread 类,重写 run 方法:定义对象,调用 start 方法 创建类实现 Runnable 接口,作为实参传递给 thr ...
- Java 多线程之 Thread 类 和 Runnable 接口初步使用
目录 Thread 类 Thread之定义线程类 Thread之开启线程 Runnable 接口 Runnable 之定义线程类 Runnable 之开启线程 @ Thread 类 Thread 类是 ...
- 多线程, Thread类,Runnable接口
多线程 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序. 单线程程序:即,若有多个任务只能依次执 ...
随机推荐
- Idea 设置Eclipse快捷键(常用)
使用Idea不习惯,特此将其配置成Eclipse风格的. 1.选择Eclipse风格,选择copy一份,可以自己重命名. 2.设置生成快捷键的快捷键(例如:Eclipse中的Alt+/) 3.设置ma ...
- springboot 启动报错Consider defining a bean of type 'com.example.springbootdruid.mapper.UserMapper' in your configurati
一.问题 springboot项目启动时报错: Field userMapper in com.example.springbootdruid.service.impl.UserServiceImpl ...
- Postgressql高可用(pgpool+异步流复制)
文档结构: 由于博主之前是Oracle dba,oracle 高可用一般是rac,以及搭建ADG,一个是基于实例的高可用,一个是基于数据的容灾,甚至也有rac+adg的方式.Postgres有同步和异 ...
- eclipse中Tomcat version 9.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, and 8 Web modules
eclipse中导入了一个别人的项目,运行时提示没有可以使用的服务器,如下: 查看了下项目属性设置中的服务器,还是提示没有可用服务器: 尝试对部署在已有服务器下的项目Add and Remove... ...
- Linux配置python
文章出处 https://www.cnblogs.com/yhongji/p/9383857.html 我这里使用的时centos7-mini,centos系统本身默认安装有python2.x,版本 ...
- 区间 GCD
区间 GCD题目描述最近 JC 同学刚学会 gcd,于是迷上了与 gcd 有关的问题.今天他又出了一道这样的题目,想要考考你,你能顺利完成吗?给定一个长度为 n 的字符串 s[1..n],串仅包含小写 ...
- 【NOIP2013】花匠
Description 花匠栋栋种了一排花,每株花都有自己的高度.花儿越长越大,也越来越挤.栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较 ...
- Docker的安装及加速器配置
简介 Docker是一个开源项目 ,其主要目标是实现轻量级的操作系统虚拟化解决方案.Docker的基础是Linux容器(LXC)等技术.在LXC的基础上Docker进行了进一步的封装,让用户不需关心容 ...
- 《java编程思想》P140-P160(第七章复部+第八章部分)
1.不用修饰符 修饰的方法或类,它们的修饰符是 默认修饰符,即 包访问权限(包内都可以用)(临时记的) 2. final数据: 对于基本类型,final使数值恒定不变,而对于对象引用,final使引用 ...
- 你真的了解Web前端开发吗?未来前端远比你想的有前途!
近几年来,随着 HTML5.JS 的流行,前端这个职业火热了起来!不少人发出疑惑,前端以后还会更有前途吗? 我只能告诉你:前端不灭 现在都明白了用户体验至上,还要用着舒服 后端提供床,前端提供颜值高的 ...