本文章是本人学习《C#多线程编程实战》的笔记,分享给有需要的朋友,欢迎大家一起学习讨论。
线程基础
为防止一个应用程序控制CPU而导致其他应用程序和操作系统本身永远被挂起这一可能情况,操作系统不得不使用某种方式将物理计算单元分割为一些虚拟的进程,并给予每个执行程序一定量的计算能力。此外,操作系统必须始终能够优先访问CPU,并能够调整不同程序访问CPU的优先级。线程正是这一概念的实现。可以认为线程是一个虚拟进程,用于独立运行一个特定的程序。
注意:线程会消耗大量的操作系统资源。多线程共享一个物理处理器将导致操作系统忙于管理这些线程,而无法运行程序。
1、使用C#创建线程
命名空间: using System;
using System.Threading;
Thread t = new Thread(SubMethod);
SubMethod:自定义方法。
2、暂停线程
Sleep:当线程处于休眠状态时,它会占用尽可能少的CPU时间,而且不用消耗操作系统资源。
3、线程等待
Join:当主程序调用了Join方法,该方法允许等待线程完成后,再继续执行主程序。
4、终止线程
Abort:当线程运行中,对线程调用Abort方法,这给线程注入了ThreadAbortException方法,导致线程被终止。这非常危险,因为该异常可以在任何时刻发生并可能彻底摧毁应用程序。另外,使用该技术也不一定总能终止线程。目标线程可以通过处理该异常并调用Thread.ResetAbort方法来拒绝被终止。因此并不推荐使用Abort方法来关闭线程。
5、线程状态
ThreadState属性,一个枚举对象。始终可以通过Thread.CurrentThread静态属性获得当前Thread对象。
6、线程优先级
Priority属性,一个枚举对象。在多核CPU上,设置优先级可能不怎么明显,因为两个线程可能运行在不同的核上,但是在单核上,CPU大部分时间运行高优先级的线程。优先级别的高低,执行效果很明显。
7、前台线程和后台线程
IsBackground:默认情况下,显式创建的线程时前台线程。通过设置IsBlackground = true来创建后台线程。
前后台线程区别:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台程序,则会直接结束工作。(如果程序定义了一个不会完成的前台程序,主程序并不会正常结束。)
8、向线程传参
使用Thread.Start方法,该方法会接收一个对象,并将该对象传递给线程。为了应用该方法,在线程中启动的方法必须接受object类型的单个参数。参考代码:
static void Main(string[] args)
{
Thread t = new Thread(Method1);
string[] paras = new string []{"black","yellow","blue" };
t.Start(paras);
Console.ReadKey();
}
static void Method1(object para)
{
if(para is Array)
{
foreach (object obj in (object[])para)
{
Console.WriteLine(obj.ToString());
}
}
}
使用lambda表达式。参考代码:
static void Main(string[] args)
{
string[] paras = new string[] { "black", "yellow", "blue" };
Thread t = new Thread(()=>Method2(paras,1));
t.Start();
Console.ReadKey();
}
static void Method2(string[] paras,int index)
{
// 超过数组边界
if (paras.Length <= index)
return;
Console.WriteLine(paras[index]);
}
注意:如果多个lambda表达式中使用相同变量,它们会共享该变量值。参考代码:
static void Main(string[] args)
{
int para = 10;
Thread t1 = new Thread(()=> Method3(para));
para = 20;
Thread t2 = new Thread(()=>Method3(para));
t1.Start();
t2.Start();
Console.ReadKey();
}
static void Method3(int para)
{
Console.WriteLine(para);
}
运行结果都是20。
使用类传参。参考代码:
static void Main(string[] args)
{
// 使用类的属性传参
ThreadMethod tm1 = new ThreadMethod();
tm1.Para = 10;
Thread t1 = new Thread(tm1.PrintNumber);
t1.Start();
// 使用构造函数传参
ThreadMethod tm2 = new ThreadMethod(20);
Thread t2 = new Thread(tm2.PrintNumber);
t2.Start();
Console.ReadKey();
}
public class ThreadMethod
{
public int Para { get; set; }
// 不带参数构造函数
public ThreadMethod() { }
// 带参数构造函数
public ThreadMethod(int para)
{
Para = para;
}
public void PrintNumber()
{
Console.WriteLine(Para);
}
}
9、使用C#中的lock关键字
表示锁定资源,资源共享情况下,为确保当一个线程使用某些资源时,同时其他线程无法使用该资源。如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这可能导致性能问题,后面进一步讲解。
10、使用Monitor类锁定资源
最常用的两个方法:Monitor.Enter方法获取锁,Monitor.Exit方法释放锁。
在使用过程中为了避免获取锁之后因为异常,导致锁无法释放,所有需要在try{} catch{}之后的finally{} 结构体中释放锁(Monitor.Exit())。
11、处理异常
在线程中使用try{} catch{} 非常重要,因为不可能在线程代码之外来捕获异常。