C#多线程学习——线程基础(C#多线程编程实战)
一、 创建和使用线程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Threading; namespace Chapter1.Recipe1 { class Program { static void Main(string[] args) { Thread t = new Thread(PrintNumbers); //创建一个新线程 t.Start(); PrintNumbers(); } static void PrintNumbers() { Console.WriteLine("Starting..."); for (int i = 1; i < 10; i++) { Console.WriteLine(i); } } } }
在创建线程前,需要引用命名空间 using System.Threading;
创建好线程后,我们可以使用一些方法对线程操作:
方法 | 操作 |
---|---|
Start | 使线程开始运行。 |
Sleep | 使线程暂停指定的一段时间。 |
Suspend | 线程到达某一安全点时暂停。 |
Abort | 线程到达某一安全点时停止。 |
Resume | 重新启动挂起的线程 |
Join | 使当前线程等待其他线程完成。 如果与超时值配合使用,当线程在分配的时间内完成时,此方法会返回 True 。 |
二、线程属性
线程还包含多个有用的属性,如下表中所示:
属性 | 值 |
---|---|
IsAlive | 如果线程处于活动状态,将包含值 True 。 |
IsBackground | 获取或设置布尔值,该值指示线程是否为或应为后台线程。 后台线程类似前台线程,但后台线程不会阻止进程停止。 属于某个进程的所有前台线程均停止后,公共语言运行时通过对仍处于活动状态的后台进程调用 Abort 方法来结束进程。 |
Name | 获取或设置线程的名称。 最常用于在调试时查找各个线程。 |
Priority | 获取或设置由操作系统用来确定线程计划优先顺序的值。 |
ThreadState | 包含描述线程状态的值。 |
三、线程的优先级
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Diagnostics; using System.Threading; namespace Chapter1.Recipe6 { class Program { static void Main(string[] args) { Console.WriteLine("Current thread priority: {0}", Thread.CurrentThread.Priority); Console.WriteLine("Running on all cores available"); RunThreads(); Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("Running on a single core"); Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); RunThreads(); } static void RunThreads() { var sample = new ThreadSample(); var threadOne = new Thread(sample.CountNumbers); threadOne.Name = "ThreadOne"; var threadTwo = new Thread(sample.CountNumbers); threadTwo.Name = "ThreadTwo"; threadOne.Priority = ThreadPriority.Highest; //这个修改了线程的优先级 threadTwo.Priority = ThreadPriority.Lowest; threadOne.Start(); threadTwo.Start(); Thread.Sleep(TimeSpan.FromSeconds(2)); sample.Stop(); } class ThreadSample { private bool _isStopped = false; public void Stop() { _isStopped = true; } public void CountNumbers() { long counter = 0; while (!_isStopped) { counter++; } Console.WriteLine("{0} with {1,11} priority " + "has a count = {2,13}", Thread.CurrentThread.Name, Thread.CurrentThread.Priority, counter.ToString("N0")); } } } }
线程有如下优先级:
-
Highest
-
AboveNormal
-
Normal
-
BelowNormal
-
Lowest
四、前后台程序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Threading; namespace Chapter1.Recipe7 { class Program { static void Main(string[] args) { var sampleForeground = new ThreadSample(10); var sampleBackground = new ThreadSample(20); var threadOne = new Thread(sampleForeground.CountNumbers); threadOne.Name = "ForegroundThread"; var threadTwo = new Thread(sampleBackground.CountNumbers); threadTwo.Name = "BackgroundThread"; threadTwo.IsBackground = true; //这里修改的线程属性为后台运行 threadOne.Start(); threadTwo.Start(); } class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations = iterations; } public void CountNumbers() { for (int i = 0; i < _iterations; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i); } } } } }
默认情况下,创建的是前台线程,手动设置IsBackground属性为true为后台线程。
前后台主要区别:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
如果程序定义了一个不会完成的前台线程,那么主程序也不会正常结束。
上面例子里,程序运行到10就结束了。
五、向线程传递参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Threading; namespace Chapter1.Recipe8 { class Program { static void Main(string[] args) { var sample = new ThreadSample(10); var threadOne = new Thread(sample.CountNumbers); //使用sample的构造函数间接传递了值 threadOne.Name = "ThreadOne"; threadOne.Start(); threadOne.Join(); Console.WriteLine("--------------------------"); var threadTwo = new Thread(Count); //用Count方法启动线程 threadTwo.Name = "ThreadTwo"; threadTwo.Start(8); //Start传入了参数 threadTwo.Join(); Console.WriteLine("--------------------------"); var threadThree = new Thread(() => CountNumbers(12)); //lamba表达式定义了不属于任何类的方法,创建一个方法,该方法使用需要的参数调用了另一个方法,并在另一个线程中运行该方法 threadThree.Name = "ThreadThree"; threadThree.Start(); threadThree.Join(); Console.WriteLine("--------------------------"); int i = 10; var threadFour = new Thread(() => PrintNumber(i)); //lamba表达式中使用任何局部变量时,C#会生辰类,将该变量做为类的属性。 i = 20; var threadFive = new Thread(() => PrintNumber(i)); //lamba表达式共享该变量值,所以都会打印20 threadFour.Start(); threadFive.Start(); } static void Count(object iterations) //此处是为了接收thread.Start(8)创建的参数object类型 { CountNumbers((int)iterations); //start传递的内容转换成了整形 } static void CountNumbers(int iterations) { for (int i = 1; i <= iterations; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i); } } static void PrintNumber(int number) { Console.WriteLine(number); } class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations = iterations; } public void CountNumbers() { for (int i = 1; i <= _iterations; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i); } } } } }
构造函数间接传递,object---->start()传递,lamba间接传递。
六、lock关键字
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Threading; namespace Chapter1.Recipe9 { class Program { static void Main(string[] args) { Console.WriteLine("Incorrect counter"); var c = new Counter(); var t1 = new Thread(() => TestCounter(c)); var t2 = new Thread(() => TestCounter(c)); var t3 = new Thread(() => TestCounter(c)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine("Total count: {0}",c.Count); Console.WriteLine("--------------------------"); Console.WriteLine("Correct counter"); var c1 = new CounterWithLock(); t1 = new Thread(() => TestCounter(c1)); t2 = new Thread(() => TestCounter(c1)); t3 = new Thread(() => TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine("Total count: {0}", c1.Count); } static void TestCounter(CounterBase c) { for (int i = 0; i < 100000; i++) //先调用增,后调用减 { c.Increment(); c.Decrement(); } } class Counter : CounterBase //不使用lock { public int Count { get; private set; } public override void Increment() { Count++; } public override void Decrement() { Count--; } } class CounterWithLock : CounterBase { private readonly object _syncRoot = new Object(); public int Count { get; private set; } public override void Increment() { lock (_syncRoot) //使用lock { Count++; } } public override void Decrement() { lock (_syncRoot) //使用lock { Count--; } } } abstract class CounterBase //创建了抽象类? { public abstract void Increment(); public abstract void Decrement(); } } }
三个线程共用counter实例,所以多个线程调用时。如果没有lock,两个线程调用后,可能会同时减去了1,但不同时加上1.或者同时加上了1,不同时减去1。
这样最后的结果多是情况下可能不是0。
七、死锁情况
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Threading; namespace Chapter1.Recipe10 { class Program { static void Main(string[] args) { object lock1 = new object(); object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); //这里创建了线程1, lock (lock2) //主线程中锁住lock2 { Thread.Sleep(1000); Console.WriteLine("Monitor.TryEnter allows not to get stuck, returning false after a specified timeout is elapsed"); if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))) //主线程等待5秒,获取lock1,当然会失败 { Console.WriteLine("Acquired a protected resource succesfully"); } else { Console.WriteLine("Timeout acquiring a resource!"); } } new Thread(() => LockTooMuch(lock1, lock2)).Start(); Console.WriteLine("----------------------------------"); lock (lock2) //这时创建的线程2,lock1在线程1中死锁。所以无法获取lock1,两个线程死锁了 { Console.WriteLine("This will be a deadlock!"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resource succesfully"); } } } static void LockTooMuch(object lock1, object lock2) //先锁住lock1,然后锁住lock2 { lock (lock1) { Thread.Sleep(1000); //等待1秒 lock (lock2); } } } }
线程1和主线程发生了简单的死锁,但是主线程是使用TryEnter。所以处理了死锁的情况,释放了lock2。但是线程1没有错误处理,所以没有释放lock1。
然后到线程2时,同样使用了lock2。但是lock1无法获取了,因为线程1没有释放?
八、错误处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Threading; namespace Chapter1.Recipe11 { class Program { static void Main(string[] args) { var t = new Thread(FaultyThread); t.Start(); t.Join(); try { t = new Thread(BadFaultyThread); t.Start(); } catch (Exception ex) { Console.WriteLine("We won't get here!"); } } static void BadFaultyThread() { Console.WriteLine("Starting a faulty thread..."); Thread.Sleep(TimeSpan.FromSeconds(2)); throw new Exception("Boom!"); } static void FaultyThread() { try { Console.WriteLine("Starting a faulty thread..."); Thread.Sleep(TimeSpan.FromSeconds(1)); throw new Exception("Boom!"); } catch (Exception ex) { Console.WriteLine("Exception handled: {0}", ex.Message); } } } }
如果直接使用线程,一般来说不要在线程中抛出异常,而是在线程代码中使用try/catch代码块
无欲速,无见小利。欲速,则不达;见小利,则大事不成。