(29)C#多线程
使用线程的原因
1.不希望用户界面停止响应。
2.所有需要等待的操作,如文件、数据库或网络访问需要一定的时间。
一个进程的多个线程可以同时运行不同cpu或多核cpu的不同内核上
注意多线程访问相同的数据必须实现同步机制
编写能够利用并行性的代码需要区分两种场景:任务并行性和数据并行性
任务并行性:使用CPU的代码被并行化,利用cpu的多个核心快速的完成包含多个任务的活动。
数据并行性:使用了数据集合,在集合上执行的工作被划分为多个任务。
任务并行性和数据并行性可以混合起来。
Parallel类(自 4.0 起可用)
Parallel类定义了并行的for和foreach的静态方法,使用多线程来完成作业。
Parallel.For()和Parallel.ForEach()方法再每次迭代中调用相同的代码。而Parallel.Invoke()方法允许同时调用不同的方法。
Parallel.Invoke()用于任务并行性,Parallel.Invoke()用于数据并行性。
Parallel.For()
using System.Threading; using System.Threading.Tasks;
class Program { static void Main(string[] args) { //参数1 int ,参数2 int,参数3 action<int> Parallel.For(3, 10, (i)=> { Console.WriteLine("id:" + i + " thread:" + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("end"); Console.ReadLine(); } }
参数3的action中可以添加一个控制状态的类型
Action<Int32,ParallelLoopState>)
使用 Break()和Stop()尽早的结束循环,在结束前启动的线程仍可以继续执行。
Break() 尽早结束当前以外的线程
Stop() 尽早结束
class Program { static void Main(string[] args) { Parallel.For(3, 100000, (i, state) => { if (i > 6) { state.Stop(); Console.WriteLine("abc"); } Console.WriteLine("id:" + i + " thread:" + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("end"); Console.ReadLine(); } }
Parallel.ForEach()
用来遍历一个能被迭代的集合,相当于一个多线程的foreach版
class Program { static void Main(string[] args) { int[] count = { 2, 3, 6, 4, 7, 25, 42, 612, 12 }; Parallel.ForEach(count, (i, state) => { Console.WriteLine("id:" + i + " thread:" + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("end"); Console.ReadLine(); } }
Parallel类返回类型
Parallel.for 和 Parallel.foreach 返回 ParallelLoopResult 类型,用来判断是否执行完成
class Program
{
static void Main(string[] args)
{
ParallelLoopResult res = Parallel.For(3, 10, (i, state) => {
Console.WriteLine("id:" + i + " thread:" + Thread.CurrentThread.ManagedThreadId);
});
Console.WriteLine(res.IsCompleted);
Console.ReadLine();
}
}
Parallel.Invoke()
public static void Invoke (params Action[] actions) ,可以调用多个不同的方法
class Program { static void Main(string[] args) { Parallel.Invoke(a,b); Console.WriteLine("end"); Console.ReadLine(); } static public void a() { Console.WriteLine("a"); } static public void b() { Console.WriteLine("b"); } }
任务
1.工厂类的方式启动一个线程
static void Main(string[] args) { var tf = new TaskFactory(); Task task = tf.StartNew(abc); Console.WriteLine("main:"+Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); } static public void abc() { Console.WriteLine("abc:"+ Thread.CurrentThread.ManagedThreadId); }
2.Task的静态Factory属性启动线程
static void Main(string[] args) { Task task = Task.Factory.StartNew(abc); Console.WriteLine("main:"+Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
3.使用Task的构造函数
实例化后不会立即启动线程,当对象调用Start()方法时才开始启动
static void Main(string[] args) { Task task =new Task(abc); task.Start(); Console.WriteLine("main:"+Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
4.使用Task的静态Run方法
static void Main(string[] args) { Task task =Task.Run(abc); Console.WriteLine("main:"+Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
5.使用Task同步运行
static void Main(string[] args) { Task task =new Task(abc); task.RunSynchronously(); Console.WriteLine("main:"+Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
6.使用单独的线程,而不是线程池
static void Main(string[] args) { Task task =new Task(abc,TaskCreationOptions.LongRunning); task.Start(); Console.WriteLine("main:"+Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
7.带返回值的任务
Func<> 返回Task<TResult>
static void Main(string[] args) { Task<int> task =Task<int>.Run(()=>{ Console.WriteLine("111"); return 5; }); Console.WriteLine(task.Result); Console.WriteLine("abc"); Console.ReadLine(); }
task.Result会等到Task执行完才会被调用相当于调用的wait,所以abc会在结果打印之后再打印
8.连续的任务
ContinueWith
static void Main(string[] args) { Task task1 = new Task(a); Task task2 = task1.ContinueWith(b); Task task3 = task2.ContinueWith(c); task1.Start(); Console.WriteLine("end"); Console.ReadLine(); } static public void a() { Console.WriteLine("a:" + Thread.CurrentThread.ManagedThreadId); } static public void b(Task t) { Console.WriteLine("b:" + Thread.CurrentThread.ManagedThreadId); } static public void c(Task t) { Console.WriteLine("c:" + Thread.CurrentThread.ManagedThreadId); }
9.连续任务控制
使用TaskContinuationOptions的枚举成员,上一步出现某种问题时,来确定是否执行现在的方法
static void Main(string[] args) { Task task1 = new Task(a); Task task2 = task1.ContinueWith(b,TaskContinuationOptions.DenyChildAttach); task1.Start(); Console.WriteLine("end"); Console.ReadLine(); }
10.任务的层次
11.
----------------------------------
Thread类
class Program { static void Main(string[] args) { Thread t1 = new Thread(M); t1.Start(); //Thread.Sleep(1000); Console.WriteLine("BBB"); Console.ReadKey(); } static void M() { Console.WriteLine("AAA"); } }
先输出AAA,还是BBB取决于操作系统调度
委托方式,输出结果和上列子相同
class Program { static void Main(string[] args) { Thread t1 = new Thread(() => Console.WriteLine("AAA")); t1.Start(); Thread.Sleep(1000); Console.WriteLine("BBB"); Console.ReadKey(); } }
给线程传递数据
方法一,启动线程时传递参数
class Program { static void Main(string[] args) { C c = new C { Message="hello" }; Thread t = new Thread(ThreadM); t.Start(c);//线程启动时传参 Console.ReadKey(); } static void ThreadM(object O) { C c = (C)O; Console.WriteLine("AAA:{0}",c.Message); } } public class C { public string Message; }
方法二、自定义类
class Program { static void Main(string[] args) { C c = new C ("hello"); Thread t = new Thread(c.ThreadMain); t.Start(); Console.ReadKey(); } } public class C { public string Message; //构造函数 public C(string Message) { this.Message = Message; } public void ThreadMain() { Console.WriteLine("AAA:{0}",Message); } }
后台线程
前台线程可以有多个,后台线程也可以有多个。只要有一个前台线程在运行,程序的Main方法就不算结束。
Thread类创建的是前台线程。线城池中的线程时后台线程。
static void Main(string[] args) { //IsBackground默认为false,表示为前台线程 Thread t = new Thread(ThreadMain) { Name="线程",IsBackground=false}; t.Start(); Console.WriteLine("AAA"); Console.ReadKey(); } public static void ThreadMain() { Console.WriteLine("{0}启动",Thread.CurrentThread.Name); Thread.Sleep(6000); } }
如果IsBackground=false,按任意键后,会等执行完sleep方法才会结束。
如果IsBackground=true,按任意键后会立即结束。
这说明,如果有一个前台线程没执行完,Main方法就算都执行完,也要等到前台程序执行完才算是结束。
线程优先级
线程的priority属性可以控制线程的优先级
它是一个枚举类型
Highest > AboveNormal > Normal > BelowNormal > Lowest
class Program { static void Main(string[] args) { Thread t1 = new Thread(ThreadMain) { Name = "A", Priority = ThreadPriority.Lowest }; Thread t2 = new Thread(ThreadMain) { Name = "B", Priority = ThreadPriority.Highest }; Thread t3 = new Thread(ThreadMain) { Name = "C", Priority = ThreadPriority.Normal }; t1.Start(); t2.Start(); t3.Start(); Console.ReadKey(); } public static void ThreadMain() { Thread.Sleep(1000); for (int i = 0; i < 5000; i++) { Console.Write("{0}", Thread.CurrentThread.Name); } } }
线程优先级高的占用的CPU会更多一些
线程问题
争用条件
如果两个或多个线程访问相同的对象,并且对共享状态的访问没有同步,就会出现争用条件。
解决办法:加一个lock锁,只能锁引用类型
class Program { static object o = new object(); static void Main(string[] args) { } public static void a() { lock (Program.o) { ..... } } }
死锁
锁定过多可能会引发死锁问题
同步