c#多线程之线程基础
作者:農碼一生
一、简介
1.为了防止一个应用程序控制CPU而导致其他应用程序和操作系统本身永远被挂起这一可能情况,操作系统不得不使用某种方式将物理计算分割为一些虚拟的进程,并给予每个执行程序一定量的计算能力。此外操作系统必须始终能够优先访问CPU,并能调整不同程序访问CPU的优先级。线程正式这一慨念的实现。
2.多线程优缺点:
多线程优点:可以同时执行多个计算任务,有可能提高计算机的处理能力,使得计算机每秒能执行越来越多的命令
多线程缺点:消耗大量的操作系统资源。多个线程共享一个处理器将导致操作系统忙于管理这些线程,而无法运行程序。
二、创建线程
class Program { static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(PrintNumbers));//无参数的委托,把方法的引用当做参数 t1.Start(); Thread t2 = new Thread(new ParameterizedThreadStart(PrintNumbers));//有参数的委托,把方法的引用当做参数 t2.Start(10); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("1.Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } } //注意:要使用ParameterizedThreadStart,定义的参数必须为object static void PrintNumbers(object count) { Console.WriteLine("2.Starting..."); for (int i = 0; i < Convert.ToInt32(count); i++) { Console.WriteLine(i); } } } }
注释:
1.我们只需指定在不同线程运行的方法名,而C#编译器会在后台创建这些对象。
2.要使用ParameterizedThreadStart,定义的参数必须为object类型。
三、暂停线程
class Program { static void Main(string[] args) { Thread t1 = new Thread(PrintNumbersWithDelay); t1.Start(); PrintNumbers(); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("1.Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine("In 1.Starting: " + i); } } static void PrintNumbersWithDelay() { Console.WriteLine("2.Starting..."); for (int i = 0; i < 10; i++) { //var a = TimeSpan.FromSeconds(2); Thread.Sleep(TimeSpan.FromSeconds(2));//暂停两秒 Console.WriteLine("In 2.Starting: " + i); } } }
注释:使用Thread.Sleep(TimeSpan.FromSeconds(2));暂停线程一段时间
四、线程等待
class Program { static void Main(string[] args) { Console.WriteLine("Starting..."); Thread t = new Thread(PrintNumbersWithDelay); t.Start(); t.Join(); //使用Join等待t完成后,再向下执行PrintNumbers,如果注释掉输出明显不同 PrintNumbers(); Console.WriteLine("Thread Complete"); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("1.Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine("In 1.Starting:" + i); } } static void PrintNumbersWithDelay() { Console.WriteLine("2.Starting..."); for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("In 2.Starting:" + i); } } }
注释:使用t.Join(); 等待t完成。
五、终止线程
class Program { static void Main(string[] args) { Console.WriteLine("Starting Program..."); Thread t1 = new Thread(PrintNumbersWithDelay); t1.Start(); Thread.Sleep(TimeSpan.FromSeconds(7));//此时t1线程会执行7秒 t1.Abort(); //使用Abort()终止线程 Console.WriteLine("Thread t1 has been aborted"); Thread t2 = new Thread(PrintNumbers); t2.Start(); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("1.Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine("In 1.Starting:" + i); } } static void PrintNumbersWithDelay() { Console.WriteLine("2.Starting..."); for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("In 2.Starting:" + i); } } }
注释:使用Thread实例的Abort方法终止线程。
六、检测线程状态
class Program { static void Main(string[] args) { Console.WriteLine("Start Program..."); Thread t1 = new Thread(PrintNumbersWithStatus); Thread t2 = new Thread(DoNothing); Console.WriteLine("t1 status:" + t1.ThreadState.ToString());//获取实例线程状态 t2.Start(); t1.Start(); for (int i = 0; i < 30; i++) { Console.WriteLine("t1 status:" + t1.ThreadState.ToString() + "\t" + "t2 status:" + t2.ThreadState.ToString()); } Thread.Sleep(TimeSpan.FromSeconds(7)); t1.Abort(); Console.WriteLine("thread t1 has been aborted"); Console.WriteLine("t1 status:" + t1.ThreadState.ToString()); Console.WriteLine("t2 status:" + t2.ThreadState.ToString()); Console.ReadLine(); } private static void PrintNumbersWithStatus() { Console.WriteLine("1.Starting..."); Console.WriteLine("In 1.Starting t1 status:" + Thread.CurrentThread.ThreadState.ToString());//获取当前线程状态 for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("In 1.Starting:" + i); } } private static void DoNothing() { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("t2 Console..."); } }
注释:使用Thread.ThreadState获取线程的运行状态。ThreadState是一个C#枚举。谨记:不要在程序中使用线程终止,否则可能会出现意想不到的结果
七、线程优先级
class Program { static void Main(string[] args) { //让操作系统的所有线程运行在多个CPU核心上 Console.WriteLine($"Current thread priority: {Thread.CurrentThread.Priority}"); Console.WriteLine("Running on all cores available");//获取实例线程状态 RunThreads(); Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("Running on a single Core"); //让操作系统的所有线程运行在单个CPU核心上 Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); RunThreads(); Console.ReadLine(); } private static void RunThreads() { var sample = new ThreadSample(); var t1 = new Thread(sample.CountNumbers); t1.Name = "Thread One"; var t2 = new Thread(sample.CountNumbers); t2.Name = "Thread Two"; t1.Priority = ThreadPriority.Highest;//使用Priority设置线程的优先级 t2.Priority = ThreadPriority.Lowest; t1.Start(); t2.Start();//此处t2优先级低于t1,t2等待t1释放资源。 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($"{Thread.CurrentThread.Name} with {Thread.CurrentThread.Priority} priority has a count={counter.ToString("N0")}"); } }
注释:单核执行多线程耗费的时间比多核的多很多。
八、前台线程和后台线程
class Program { static void Main(string[] args) { var sampleForground = new ThreadSample(10); var sampleBackground = new ThreadSample(20); var t1 = new Thread(sampleForground.CountNumbers);//方法的引用 t1.Name = "ForegroundThread"; //没有明确声明的均为前台线程 var t2 = new Thread(sampleBackground.CountNumbers); t2.Name = "BackgroundThread"; t2.IsBackground = true; //设置为后台线程 t1.Start(); t2.Start(); Console.ReadLine(); } } class ThreadSample { private readonly int _iteration; public ThreadSample(int iteration) { _iteration = iteration; } public void CountNumbers() { for (int i = 0; i < _iteration; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } }
注释:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
九、向线程传递参数
class Program { static void Main(string[] args) { ThreadSample sample = new ThreadSample(5); Thread t1 = new Thread(sample.CountNumbers); t1.Name = "ThreadOne"; t1.Start(); t1.Join(); Console.WriteLine("--------------------------"); Thread t2 = new Thread(Count); t2.Name = "ThreadTwo"; t2.Start(3); t2.Join(); Console.WriteLine("--------------------------"); //使用lambda表达式引用另一个C#对方的方式被称为闭包。当在lambda表达式中使用任何局部变量时,C#会生成一个类,并将该变量作为该类的一个属性,但是我们无须定义该类,C#编译器会自动帮我们实现 Thread t3 = new Thread(() => CountNumbers(5)); t3.Name = "ThreadThree"; t3.Start(); t3.Join(); Console.WriteLine("--------------------------"); int i = 10; Thread t4 = new Thread(() => PrintNumber(i)); i = 20; Thread t5 = new Thread(() => PrintNumber(i)); t4.Start(); t5.Start(); //t4, t5都会输出20, 因为t4,t5没有Start之前i已经变成20了 Console.ReadKey(); } static void Count(object iterations) { CountNumbers((int)iterations); } static void CountNumbers(int iterations) { for (int i = 1; i <= iterations; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } static void PrintNumber(int number) { Console.WriteLine(number); } } class ThreadSample { private readonly int _iteration; public ThreadSample(int iteration) { _iteration = iteration; } public void CountNumbers() { for (int i = 1; i <= _iteration; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } }
十、使用C# Lock 关键字
class Program { static void Main(string[] args) { Console.WriteLine("Incorrect Counter"); Counter c1 = new Counter(); var t1 = new Thread(() => TestCounter(c1)); var t2 = new Thread(() => TestCounter(c1)); var t3 = new Thread(() => TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"Total Count: {c1.Count}"); Console.WriteLine("------------------------"); //使用LOCK关键字,Count同一时刻只允许一个线程访问 Console.WriteLine("Correct counter"); CounterWithLock c2 = new CounterWithLock(); t1 = new Thread(() => TestCounter(c2)); t2 = new Thread(() => TestCounter(c2)); t3 = new Thread(() => TestCounter(c2)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"Total count:{c2.Count}"); Console.ReadLine(); } static void TestCounter(CounterBase c) { for (int i = 0; i < 100000; i++) { c.Increment(); c.Decrement(); } } //子类 class Counter : CounterBase { public int Count { get; private set; } //重写基类方法 public override void Decrement() { Count--; } public override void Increment() { Count++; } } //子类 class CounterWithLock : CounterBase { private readonly object _asyncRoot = new object(); public int Count { get; private set; } //重写基类方法 public override void Decrement() { lock (_asyncRoot) { Count--; } } public override void Increment() { lock (_asyncRoot) { Count++; } } } //基类 abstract class CounterBase { public abstract void Increment(); public abstract void Decrement(); } } class ThreadSample { private readonly int _iteration; public ThreadSample(int iteration)//构造函数 { _iteration = iteration; } public void CountNumbers() { for (int i = 1; i <= _iteration; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } }
注释:不加锁,得出的结果不确定,竞争条件下很容易出错。加锁得出的结果是正确的,但是性能受到了影响
十一、使用Monitor类锁定资源
class Program { static void Main(string[] args) { object lock1 = new object(); object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Thread.Sleep(1000); Console.WriteLine("Monitor.TryEnter allows not to get stuck, returning false after a specified timeout is elapsed"); //直接使用Monitor.TryEnter, 如果在第二个参数之前还未获取到lock保护的资源会返回false if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))) { Console.WriteLine("Acquired a protected resource successfully"); } else { Console.WriteLine("Timeout acquiring a resource"); } } new Thread(() => LockTooMuch(lock1, lock2)).Start(); Console.WriteLine("-----------------------------"); /* 下面代码会造成死锁, 所以注释掉 lock (lock2) { Console.WriteLine("This will be a deadlock!"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resource successfully"); } } */ } static void LockTooMuch(object lock1, object lock2) { lock (lock1) { Thread.Sleep(1000); lock (lock2); } } }
注释:Monitor.TryEnter在指定的时间内尝试获取指定对象上的排他锁
十二、处理异常
class Program { static void Main(string[] args) { Thread 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 bad faulty thread....."); Thread.Sleep(TimeSpan.FromSeconds(2)); //这个异常主线程无法捕捉到,因为是在子线程抛出的异常。需要在子线程中加入try...catch捕获异常 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: {ex.Message}"); } } }
到此这篇关于c#多线程之线程基础的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。