Thrad类提供了:在不同线程上执行方法的能力
Thread类位于System.Threading名称空间下,学会使用一下几点技能,便可基本掌握最简单的多线程操作
知识点1:创建并启动线程
class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
Console.WriteLine("thread1 is ready to running....");
thread1.Start();
Console.ReadLine();
}
}
class CommonService
{
public static void GetConnection()
{
Console.WriteLine("connect success....");
}
}
说明:1、new一个Thread对象,便创建了一个新的线程;2、调用Start方法,将创建的线程的状态设置为running状态;操作系统会将该线程视为执行,然后寻找空闲时间,在该线程上执行GetConnection方法。
知识点2(不常用):暂停(挂起)某个线程一段时间
class Program
{
static void Main(string[] args)
{
Thread.Sleep(new TimeSpan(0,0,5));//将当前线程暂停五秒(暂停的是,Main方法所在的主线程)
Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
Console.WriteLine("thread1 is ready to running....");
thread1.Start();
Console.ReadLine();
}
}
class CommonService
{
public static void GetConnection()
{
Thread.Sleep(TimeSpan.FromSeconds(3));//暂停的是GetConnection方法所在的线程
Console.WriteLine("connect success....");
}
}
说明:使用Thread类的静态方法--Sleep(),系统会自动判断执行的代码所处在哪一个线程"。1、在主线程中使用了Sleep,那么整个应用会停留在该位置"假死"到指定的时间,才继续向下执行;2、在子线程中使用Sleep,那么子线程会暂停操作,到指定时间才继续执行。开发工作中,不建议使用Sleep()方法。
知识点3(不常用):在一个线程中等待另个线程执行完成再继续
当子线程创建并启动后,主线程同一时刻也在运行;子线程何时执行完毕是不确定的;如果我们希望等待某个线程执行完后,才继续执行另一个线程的代码,就需要使用Join()方法。
class Program
{
static void Main(string[] args)
{
Thread.Sleep(new TimeSpan(0,0,5));//将Main方法所在的主线程暂停五秒
Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
Console.WriteLine("thread1 is ready to running....");
thread1.Start();
thread1.Join();//join方法有两个重载,这里使用无参数形式的Join()方法来等待线程执行结束。
Console.WriteLine("main is ready to completed ....");
Console.ReadLine();
}
}
class CommonService
{
public static void GetConnection()
{
Thread.Sleep(TimeSpan.FromSeconds(3));//暂停的是GetConnection方法所在的线程
Console.WriteLine("connect success....");
}
}
说明:从执行结果可以看出,使用Join()方法后,线程之间便存在了一种同步关系--即:你先干完,我再干。
知识点4:前台线程与后台线程
class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(new ThreadStart(CommonService.GetConnection));
/**
* 可以通过解除下面注释来观察前台线程与后台线程的区别:
* 未解除注释--Main方法所在的主线程率先退出;GetConnection方法所在的子线程是前台线程,应用程序会等待子线程中的代码执行完毕后才结束进程。
* 解除注释--Main方法所在的主线程率先退出;GetConnection方法所在的子线程是后台线程,应用程序检测进程中没有正在执行的前台线程后,直接结束进程。
*/
//thread.IsBackground = true;
thread.Start();
}
}
class CommonService
{
public static void GetConnection()
{
Console.WriteLine($"当前正在执行方法所在的线程是否为后台线程:{Thread.CurrentThread.IsBackground}");
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("connect success....");
}
}
以控制台程序为例:1、手动创建的线程,默认也是一个"前台线程";3、如果指定IsBackground=true,那么线程被指定为后台线程;4、应用程序会等待所有的前台线程执行完毕后再结束工作(如果程序中只剩下后台线程的话,应用程序会直接结束工作)
知识点5:给线程传递参数
使用Thread对象的Start方法的重载形式:通过给Start()方法传递参数,达到给线程传递参数
错误内容:System.InvalidOperationException:“该线程是用不接受参数的 ThreadStart 委托创建的。ThreadStart不接受参数,所以需要一个能够接收参数的委托:ParameterizedThreadStart
方式1:使用ParameterizedThreadStart创建线程、并通过给Start()方法传递参数,来给线程传递参数
private static void TestThreadBase()
{
Thread thread = new Thread(new ParameterizedThreadStart(CommonService.ConnectionWithTarget));
thread.Start(12);
}
class CommonService
{
public static void ConnectionWithTarget(object targetName)
{
Console.WriteLine($"线程 {targetName} 连接成功....");
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.WriteLine($"线程 {targetName} 退出 ...");
}
}
/*
线程 12 连接成功....
线程 12 退出 ...
*/
使用ParameterizedThreadStart委托的方式给线程传递参数,那么执行线程的方法的参数列表就需要和ParameterizedThreadStart委托的参数列表匹配--即:需要改方法定义一个Object类型的形参;假如我希望执行线程的方法的参数是Student类型的(如下),那么使用ParameterizedThreadStart委托,还能奏效么?
准备一个入参为Student类型的方法
class CommonService
{
public static void ShowTargetInfo(Student student)
{
Console.WriteLine($"{student.StudentName}的性别是{student.StudentSex},身高是{student.StudentHeight}");
}
}
class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public double StudentHeight { get; set; }
public char StudentSex { get; set; }
}
尝试给ParameterizedThreadStart委托添加ShowTargetInfo方法
上面的报错告诉我们,ShowTargetInfo方法不匹配ParameterizedThreadStart委托,所以我们需要转换思路:使用Lambda表达式来实例化ParameterizedThreadStart委托;然后再在Lambda表达式中调用ShowTargetInfo方法,这样ShowTargetInfo也算是在该线程上运行了。
方式2:使用Lambda表达式实例化委托类型,然后在Lambda表达式中调用方法,达到给线程传递参数的目的
private static void TestThreadBase()
{
Student student = new Student() { StudentId = 11, StudentName = "小哇", StudentSex = '男', StudentHeight = 170.5 };
//方式1
Thread thread1 = new Thread(new ThreadStart(()=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
}));
thread1.Start();
//方式2
Thread thread2 = new Thread(new ParameterizedThreadStart(item=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
}));
thread1.Start();
//方式3
Thread thread3 = new Thread(new ParameterizedThreadStart(item =>
{
var stu = item as Student;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(stu);
}));
thread3.Start(student);
//方式1 简写形式 Lambda表达式隐式创建了ThreadStart委托(也是我最常用的给线程传递参数的形式)
Thread thread4 = new Thread(()=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
});
thread4.Start();
//方式2 简写形式 Lambda表达式隐式创建了ParameterizedThreadStart委托
Thread thread5 = new Thread(item =>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
});
thread5.Start();
//方式3 简写形式 Lambda表达式隐式创建了ParameterizedThreadStart委托
Thread thread6 = new Thread(item =>
{
var stu = item as Student;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(stu);
});
thread5.Start(student);
}
向线程传递参数,本质上是向线程所执行的方法传入实参;利用Lambda表达式调用目标方法的形式,使目标方法参数列表的灵活度高,不受限制。
知识点6:在多线程中处理异常
当我们创建了一个线程并启动该线程,那么在该线程中一旦发生了异常,会发生什么后果呢?
在工作线程中(即:非主线程)发生了异常,会导致程序崩溃
private static void TestThreadException()
{
new Thread(()=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
Thread.Sleep(3000);
throw new Exception($"从线程{Thread.CurrentThread.ManagedThreadId}中抛出异常....");
}).Start();
}
static void Main(string[] args)
{
TestThreadException();
Console.ReadLine();
}
分析:Lambda表达式中的代码片段是运行在子线程中的,三秒后该线程中抛出异常;由于没有处理该异常控制台程序直接崩溃。
在子线程的代码外部试图捕获异常也是徒劳的
static void Main(string[] args)
{
try
{
TestThreadException();
}
catch (Exception ex)
{
Console.WriteLine("捕捉到了子线程的异常{0}", ex.Message);
}
Console.ReadLine();
}
分析:本来我们以为在Main()方法中使用try..catch块儿来试图捕获子线程中的异常,但实际却并未能捕捉到该异常,程序照常崩溃
在子线程中处理异常,才是多线程中处理异常的正确方式
private static void TestThreadException()
{
new Thread(() =>
{
try
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
Thread.Sleep(3000);
throw new Exception($"从线程{Thread.CurrentThread.ManagedThreadId}中抛出异常....");
}
catch (Exception ex)
{
Console.WriteLine("捕捉到了子线程的异常{0}", ex.Message);
}
}).Start();
}
/*
3 is running .....
捕捉到了子线程的异常从线程3中抛出异常....
*/
多线程编程处理异常:不要在线程中抛出异常,而是使用try..catch块儿。
以上便是对多线程基础的知识点的总结,记录下来以便以后查阅。