随着.net版本不断升级,目前多种多线程实现方法
一 .Thread 最基本
1.优缺点
优点--Thread API丰富
缺点-- 1.线程资源是操作系统管理的,对API响应并不灵敏,(也就是调用一次提供的API可能不会立即响应)难以控制
2.线程启动数量是没有控制的,可能会导致死机等意外发生
2.Thread对象实例化方法(四种)
2.1声明一个无参的、返回值为void的委托ThreadStart,委托内含一个静态方法;
2.2 声明一个无参的、返回值为void的委托ThreadStart,委托内含一个对象方法;
2.3 直接使用匿名委托;
2.4 直接使用Lambda表达式;
namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"主线程,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread thread1 = new Thread(new ThreadStart(NewThreadDisplay));//第一种
AnotherObject another = new AnotherObject();
Thread thread2 = new Thread(new ThreadStart(another.AnotherThreadDisplay));//第二种
Thread thread3 = new Thread(delegate() { Console.WriteLine($"子线程3,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); } );//第三种
Thread thread4 = new Thread(()=> Console.WriteLine($"子线程4,ThreadId: {Thread.CurrentThread.ManagedThreadId}"));//第四种
thread1.Start();//启动子线程1
thread2.Start();//启动子线程2
thread3.Start();//启动子线程3
thread4.Start();//启动子线程4
Console.ReadKey();
}
static void NewThreadDisplay()
{
Console.WriteLine($"子线程1,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
}
}
class AnotherObject
{
public void AnotherThreadDisplay()
{
Console.WriteLine($"子线程2,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
}
}
}
输出结果:
主线程,ThreadId: 1
子线程3,ThreadId: 5
子线程4,ThreadId: 6
子线程1,ThreadId: 3
子线程2,ThreadId: 4
可以看出多线程启用时无序的,即使一个线程在是先start的,执行也可能在后start的线程后面;并且每次执行的先后顺序都不一定相同
3.关于线程优先级 Priority属性
Priority属性是一个ThreadPriority型枚举,列举了5个优先等级:Highest、AboveNormal、Normal(普通线程默认)、BelowNormal、Lowest
4.关于线程的状态
通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。
通过CurrentContext可以获取线程当前的上下文。
CurrentThread是最常用的一个属性,它是用于获取当前运行的线程。
5.列举一些API(可能有坑)
终止-Abort() GetDomain()-返回当前线程正在其中运行的当前域 GetDomainId() Interrupt()-中断处于 WaitSleepJoin 线程状态的线程
Join() - 已重载。 阻塞调用线程,直到某个线程终止时为止 Resume()-继续运行已挂起的线程 Start()-执行本线程 Suspend()-挂起当前线程
Sleep()- 把正在运行的线程挂起一段时间
6.关于前台线程和后台线程
前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程
后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。
通过BeginXXX方法运行的线程都是后台线程。
注1:后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。
二. ThreadPool
.NetFramework 2.0 新增
基于池化资源管理设计思想,线程是一种资源,每次要用线程,就去申请一个线程,使用完释放;池化就是一种容器,容器提前申请5个线程,程序需要使用线程直接找容器获取,
用完再放回(空置状态,避免频繁申请和销毁,容易还会根据限制的数量去申请和释放)
优点:1线程复用 2限制最大线程数量
缺点:API太少了,在线程顺序控制上弱,用起来不方便
三.Task--.NetFramework 3.0 新增
比较常用的多线程方式,全部是线程池线程、提供丰富API
Task线程全部是线程池线程、提供丰富API
Action action = o=>{xxxx;}
Task task =new Task(action);
task.Start();
举例:
namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"主线程开始,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Task task1 = new Task(() => Console.WriteLine($"子线程1,ThreadId: {Thread.CurrentThread.ManagedThreadId}"));
task1.Start();
//也可以直接创建并启用
List<Task> taskList = new List<Task>();
taskList.Add(Task.Run(() => Console.WriteLine($"子线程2,ThreadId: {Thread.CurrentThread.ManagedThreadId}")));
taskList.Add(Task.Run(() => Console.WriteLine($"子线程3,ThreadId: {Thread.CurrentThread.ManagedThreadId}")));
taskList.Add(Task.Run(() => Console.WriteLine($"子线程4,ThreadId: {Thread.CurrentThread.ManagedThreadId}")));
Task.WaitAny(taskList.ToArray());////阻塞当前线程,直到任一任务结束
Console.WriteLine($"有一个子线程执行完毕,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Task.WaitAll(taskList.ToArray());////阻塞当前线程,直到全部任务结束
Console.WriteLine($"所有子线程执行完毕,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"主线程结束,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Console.ReadKey();
}
}
}
执行结果:
主线程开始,ThreadId: 1
子线程1,ThreadId: 4
子线程2,ThreadId: 7
子线程3,ThreadId: 8
子线程4,ThreadId: 4
有一个子线程执行完毕,ThreadId: 1//阻塞了主线程执行!
所有子线程执行完毕,ThreadId: 1////阻塞了主线程执行!
主线程结束,ThreadId: 1
通过结果发现,无论怎么执行,最后三行输出的顺序都是一样的!可见通过waitAny()、waitAll()方法可以阻塞主线程的执行,已经等达到一些顺序控制的目的了!
注:1.尽量不要线程套线程,有更优秀的方法
2.子线程不能直接操作界面
等全部任务完成后启动一个新的task完成后续动作
TaskFactory taskFactory =new TaskFactory();
taskFactory.ContinueWhenAll(taskList.ToArray(),tArray=>{
xxxx
})
等任一任务完成后启动一个新的task完成后续动作
taskFactory.ContinueWhenAny(taskList.ToArray(),tArray=>{
xxxx
})
//continue的后续线程,不可能是主线程,其余皆可能
四.Parallel并行编程
主线程也参与计算,介于线程,节约一个线程
通过指定ParallelOptions控制最大并发数量
Parallel.Invoke(()=>{111;},()=>{222;},()=>{333;})
namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"主线程开始,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 4},
() => {
Console.WriteLine($"子线程1,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000); },
() => {
Console.WriteLine($"子线程2,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);},
() => {
Console.WriteLine($"子线程3,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
},
() => {
Console.WriteLine($"子线程4,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
},
() => {
Console.WriteLine($"子线程5,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
});
Console.WriteLine($"主线程结束,ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Console.ReadKey();
}
}
}
输出结果:
主线程开始,ThreadId: 1
子线程1,ThreadId: 1
子线程2,ThreadId: 3
子线程3,ThreadId: 5
子线程4,ThreadId: 4
子线程5,ThreadId: 1
主线程结束,ThreadId: 1
特点:主线程(1)也参与计算,节约一个线程;可以通过ParallelOptions控制最大并发数量(本例设置为4),子线程5与线程1使用了同一个线程,所以5等1执行完了之后才会打印,同理主线程结束也在最后完成;
五、BeginInvoke()线程封装相关的
委托.BeginInvoke()自动启用异步多线程,并且带 回调 BeginInvoke()
3.利用委托方式在子线程中改变主线程UI的内容
private delegate void delegateUpdateLabel(string text);
private void UpdateLabel(string text)
{
if (this.updateProcess_label.InvokeRequired)
{
Invoke(new delegateUpdateLabel(UpdateLabel), new object[] { text });
}
else
{
updateProcess_label.Text = text;
}
}
IAsyncResult asyncResult1 = action.BeginInvoke("", null, null);
IAsyncResult asyncResult2 = func.BeginInvoke(null, null);
判断异步线程完成方式:
1.利用asyncResult.IsCompleted 2.利用信号量asyncResult.AsyncWaitHandle.WaitOne(),可以做超时判断 ---waitOne(-1)一直等待 waitOne(100)等待100ms
带返回值的异步调用方法 returnValue = func.EndInvoke(asyncResult2) EndInvoke()也能用在回调里,但只能用一次