前言
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
本文主要介绍.NET(C#) 中使用Thread、Task或Parallel实现多线程的总结,以及相关的示例代码。
一、Thread的使用
Thread
是C#语言对线程对象的封装 ,从.NET 1.0版本就开始存在。
1、Thread初始化
using System;
using System.Threading;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name)
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
thread.Name = name;
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
}
static void Main(string[] args)
{
Action action = () => TaskFunc("action");
ThreadStart threadStart = () => TaskFunc("线程2");
//Thread thread = new Thread(action);//不可以这样初始化
Thread thread = new Thread(() => TaskFunc("线程1"));
thread.Start();
Thread thread2 = new Thread(threadStart);
thread2.Start();
Console.ReadKey();
}
}
}
2、Thread的常用操作
using System;
using System.Threading;
namespace ConsoleApplication
{
class Program
{
static bool isRun = false;
private static void TaskFunc(string name)
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
thread.Name = name;
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
while (isRun)
{
Thread.Sleep(500);
}
}
static void Main(string[] args)
{
isRun = true;
Thread thread = new Thread(() => TaskFunc("线程1"));
//1、线程启动
thread.Start();
//thread.Suspend();//线程挂起_已弃用
//thread.Resume();//唤醒线程_已弃用
//2、线程销毁
try
{
//thread.Abort();//销毁,方式是抛异常 不推荐使用
}
catch (Exception)
{
//Thread.ResetAbort();//取消Abort异常
}
//3、线程等待
thread.Join(500);//最多等500
Console.WriteLine("等待500ms");
isRun = false;
thread.Join();//当前线程等待thread完成
//4、判断线程是否停止
//while (thread.ThreadState != ThreadState.Stopped)
//{
// Thread.Sleep(100);//当前线程 休息100ms
//}
//5、设置后台线程
//默认是前台线程,启动之后一定要完成任务的,阻止进程退出
//thread.IsBackground = true;//指定后台线程:进程退出则退出
//6、设置线程优先级
//thread.Priority = ThreadPriority.Highest;//线程优先级
//CPU会优先执行 Highest 不代表说Highest就最先
Console.ReadKey();
}
}
}
3、前台线程与后台线程的区别Thread默认是前台线程,启动之后一定要完成任务的,阻止进程退出,就是一定要线程运行完毕进程才会退出。而后台线程是进程的退出线程也退出。
4、Thread实现回调
using System;
using System.Threading;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name)
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
thread.Name = name;
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
}
//启动子线程计算--完成委托后,该线程去执行后续回调委托
private static void ThreadWithCallback(Action act, Action callback)
{
Thread thread = new Thread(() =>
{
act.Invoke();
callback.Invoke();
});
thread.Start();
}
///
/// 又要结果 要不阻塞
///
///
///
///
private static Func ThreadWithReturn(Func func)
{
T t = default(T);
Thread thread = new Thread(() =>
{
t = func.Invoke();
});
thread.Start();
return () =>
{
thread.Join();
return t;
};
}
static void Main(string[] args)
{
Thread thread = new Thread(() => TaskFunc("线程1"));
//不带返回值回调
ThreadWithCallback(() => Console.WriteLine($"主线程执行 {Thread.CurrentThread.ManagedThreadId.ToString("00")}")
, () => Console.WriteLine($"回调执行 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
//带返回值回调
Func func = ThreadWithReturn(() =>
{
Thread.Sleep(1000);
return DateTime.Now;
});
Console.WriteLine("返回值:" + func.Invoke());
Console.ReadKey();
}
}
}
Task
是.NET 4.0加入的,与线程池ThreadPool
的功能类似,用Task
开启新任务时,会从线程池中调用线程,而Thread
每次实例化都会创建一个新的线程。
我们可以说Task
是一种基于任务的编程模型。它与Thread
的主要区别是,更加方便对线程进程调度和获取线程的执行结果。并且Task
是针对多核有优化。
1、Task启动的方式
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name)
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
//thread.Name = name;//只能设置一次
Console.WriteLine(thread.Name);
Console.WriteLine(name);
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
//执行耗时间耗资源的任务
Console.WriteLine(DateTime.Now.Ticks);
}
static void Main(string[] args)
{
//三种Task启动的方式
var t1 = Task.Run(() => TaskFunc("线程1"));
var t2 = Task.Factory.StartNew(() => TaskFunc("线程2"));
var t3 = new Task(() => TaskFunc("线程3"));
t3.Start();
//启动带回调
var t4 = Task.Run(() => TaskFunc("线程4")).ContinueWith(t => { Console.WriteLine(t.AsyncState); });
Task.WaitAll(t1, t2, t3, t4);
Console.ReadKey();
}
}
}
2、Thread.Sleep()和Task.Delay()的使用
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name="")
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
//thread.Name = name;//只能设置一次
Console.WriteLine(thread.Name);
Console.WriteLine(name);
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
//执行耗时间耗资源的任务
Console.WriteLine(DateTime.Now.Ticks);
}
static void Main(string[] args)
{
//同步延时,阻塞主线程
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Thread.Sleep(500);
stopwatch.Stop();
Console.WriteLine("stopwatch:" + stopwatch.ElapsedMilliseconds);
TaskFunc();
//异步延时,不阻塞主线程
Stopwatch stopwatch2 = new Stopwatch();
stopwatch2.Start();
var t1= Task.Delay(500).ContinueWith(t =>
{
stopwatch2.Stop();
Console.WriteLine("stopwatch2:" + stopwatch2.ElapsedMilliseconds);
});
TaskFunc();
//同步+异步延时,不阻塞主线程
Stopwatch stopwatch3 = new Stopwatch();
stopwatch3.Start();
var t2=Task.Run(() =>
{
Thread.Sleep(500);
stopwatch3.Stop();
Console.WriteLine("stopwatch3:" + stopwatch3.ElapsedMilliseconds);
TaskFunc();
});
Task.WaitAll(t1, t2);
}
}
}
3、通过判断线程状态来控制线程最大运行数
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name = "")
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
//thread.Name = name;//只能设置一次
Console.WriteLine(thread.Name);
Console.WriteLine(name);
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
//执行耗时间耗资源的任务
Console.WriteLine(DateTime.Now.Ticks);
}
static void Main(string[] args)
{
var maxCount = 2;
List list = new List();
for (int i = 0; i < 100; i++)
{
list.Add(i);
}
Action action = i =>
{
TaskFunc();
Thread.Sleep(10);
};
List taskList = new List();
foreach (var i in list)
{
int k = i;
taskList.Add(Task.Run(() => action.Invoke(k)));
if (taskList.Count > maxCount)
{
Task.WaitAny(taskList.ToArray());
taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
Console.WriteLine("运行中的任务数:" + taskList.Count);
}
}
//异步等待其全部执行完毕,不阻塞线程
Task wTask = Task.WhenAll(taskList.ToArray());
//wTask.ContinueWith()...
//死等线程全部执行完毕,阻塞后面的线程
Task.WaitAll(taskList.ToArray());
//Task.WaitAll()和Task.WhenAll()区别一个阻塞线程,一个不阻塞
}
}
}
Parallel
是并行编程,在Task的基础上做了封装,.NET FrameWork 4.5之后的版本可用,调用Parallel
线程参与执行任务。
1、Parallel.For()和Parallel.ForEach()的使用
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name = "")
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
//thread.Name = name;//只能设置一次
Console.WriteLine(thread.Name);
Console.WriteLine(name);
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
//执行耗时间耗资源的任务
Console.WriteLine(DateTime.Now.Ticks);
}
static void Main(string[] args)
{
Parallel.For(0, 5, i => { Console.WriteLine("i="+i); TaskFunc(); });
Parallel.ForEach(new string[] { "0", "1", "2", "3", "4" }, j => { Console.WriteLine("j="+j); TaskFunc(); });
}
}
}
2、ParallelOptions 控制并发数量
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
private static void TaskFunc(string name = "")
{
//获取正在运行的线程
Thread thread = Thread.CurrentThread;
//设置线程的名字
//thread.Name = name;//只能设置一次
Console.WriteLine(thread.Name);
Console.WriteLine(name);
//获取当前线程的唯一标识符
int id = thread.ManagedThreadId;
//获取当前线程的状态
System.Threading.ThreadState state = thread.ThreadState;
//获取当前线程的优先级
ThreadPriority priority = thread.Priority;
string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
"Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
state, priority);
Console.WriteLine(strMsg);
//执行耗时间耗资源的任务
Console.WriteLine(DateTime.Now.Ticks);
}
static void Main(string[] args)
{
//state.Break()和state.Stop() 都不推荐用,异常情况处理较麻烦
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 2;//控制并发数量
Parallel.For(1, 12, parallelOptions, (i, state) =>
{
//state.Stop();/*
//调用 Stop 方法指示尚未开始的循环的任何迭代都无需运行。 它可以有效地取消循环的任何其他迭代。 但是,它不会停止已经开始执行的任何迭代。
//调用 Stop 方法会导致此 IsStopped 属性返回到 true 仍在执行的循环的任何迭代。 这对于长时间运行的迭代特别有用,它可以检查 IsStopped 属性并在其值为时提前退出 true 。
//Stop 通常在基于搜索的算法中使用,在找到结果后,不需要执行其他迭代。
//state.Break();
//Break 指示应运行当前迭代之后的任何迭代。 它可以有效地取消循环的任何其他迭代。 但是,它不会停止已经开始执行的任何迭代。 例如,如果 Break 是从从0到1000的并行循环的第100迭代调用的,则所有小于100的迭代仍应运行,但不会执行从101到1000的迭代。
//对于可能已在执行的长时间运行的迭代, Break LowestBreakIteration 如果当前索引小于的当前值,则将属性设置为当前迭代的索引 LowestBreakIteration 。 若要停止其索引大于从争用执行的最低中断迭代的迭代,应执行以下操作:
//检查属性是否 ShouldExitCurrentIteration 为 true 。
//如果其索引大于属性值,则从迭代退出 LowestBreakIteration 。
//说明如示例所示。
//Break 通常在基于搜索的算法中采用,其中排序在数据源中存在。
TaskFunc();
});
}
}
}
转自:levi
链接:cjavapy.com/article/2499/