1、命名空间与继承
命名空间:System.Threading
继承:Object→CriticalFinalizerObject→Thread
2、知识点
2.1 Thread.Sleep(0);
CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。如果一个线程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU
Windows中CPU根据优先级分配。Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”。
2.2 线程生命周期:
线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。
下面列出了线程生命周期中的各种状态:
- 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
- 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
- 不可运行状态:下面的几种情况下线程是不可运行的:
- 已经调用 Sleep 方法
- 已经调用 Wait 方法
- 通过 I/O 操作阻塞
- 死亡状态:当线程已完成执行或已中止时的状况。
2.3 前台线程和后台线程
- 前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程
- 后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。
注意:后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。
2.4 跨线程访问
产生错误的原因:textBox1是由主线程创建的,thread线程是另外创建的一个线程,在.NET上执行的是托管代码,C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件。
解决方案:
1、在窗体的加载事件中,将C#内置控件(Control)类的CheckForIllegalCrossThreadCalls属性设置为false,屏蔽掉C#编译器对跨线程调用的检查。
private void Form1_Load(object sender, EventArgs e)
{
//取消跨线程的访问
Control.CheckForIllegalCrossThreadCalls = false;
}
2、使用回调函数Invoke
如果要在遵守.NET安全标准的前提下,实现从一个线程成功地访问另一个线程创建的空间,要使用C#的方法回调机制。
3、使用组件Background
3、常用属性和方法
属性 | 描述 |
---|---|
CurrentContext | 获取线程正在其中执行的当前上下文。 |
CurrentThread | 获取当前正在运行的线程。 |
ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
IsAlive | 获取一个值,该值指示当前线程的执行状态。 |
IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Name | 获取或设置线程的名称。 |
Priority | 获取或设置一个值,该值指示线程的调度优先级。 |
ThreadState | 获取一个值,该值包含当前线程的状态。 |
方法 | 描述 |
---|---|
public void Abort() | 在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程。 |
public static void Sleep( int millisecondsTimeout ) | 让线程暂停一段时间。 |
public void Start() | 开始一个线程。 |
public static void ResetAbort() | 取消为当前线程请求的 Abort。 |
public void Join() | 在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。此方法有不同的重载形式。 |
Resume() | 继续运行已挂起的线程。 |
Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
Thread.Sleep() //当前线程sleep,在Main()就是主线程,在其他线程函数里就是当前分支线程sleep
4、类型
4.1 无参数委托类型
- 委托定义:public delegate void ThreadStart();
//声明委托类型
//无参委托类型声明实例化
ThreadStart start = new ThreadStart(ThreadProc);
//Thread实例化
Thread thread = new Thread(start);
//线程启动
thread .Start();
//无参方法
public static void ThreadProc()
{
for (int i = 0; i <= 300; i++)
{
Console.WriteLine("这是无参的方法");
}
}
- 除了可以运行静态的方法,还可以运行实例方法[类的方法]
class Program
{
static void Main(string[] args)
{
//创建ThreadTest类的一个实例
ThreadTest test=new ThreadTest();
//调用test实例的MyThread方法
Thread thread = new Thread(new ThreadStart(test.MyThread));
//启动线程
thread.Start();
Console.ReadKey();
}
}
class ThreadTest
{
public void MyThread()
{
Console.WriteLine("这是一个实例方法");
}
}
如果为了简单,也可以通过匿名委托或Lambda表达式来为Thread的构造方法赋值
static void Main(string[] args)
{
//通过匿名委托创建
Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通过匿名委托创建的线程"); });
thread1.Start();
//通过Lambda表达式创建
Thread thread2 = new Thread(() => Console.WriteLine("我是通过Lambda表达式创建的委托"));
thread2.Start();
Console.ReadKey();
}
4.2 有参数委托类型
- 委托定义:public delegate void ParameterizedThreadStart(Object obj)
注意:ParameterizedThreadStart委托的参数类型必须是Object的。如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。
class Program
{
static void Main(string[] args)
{
//通过ParameterizedThreadStart创建线程
Thread thread = new Thread(new ParameterizedThreadStart(Method));
//给方法传值
thread.Start("这是一个有参数的委托");
Console.ReadKey();
}
/// <summary>
/// 创建有参的方法
/// 注意:方法里面的参数类型必须是Object类型
/// </summary>
/// <param name="obj"></param>
static void Method(object obj)
{
Console.WriteLine(obj);
}
}
5、线程同步
Mutex 跟 lock 相似,但是 Mutex 支持多个进程。Mutex 大约比 lock 慢 20 倍
5.1 Lock关键字
5.2 Mutex互斥锁
- Mutex 中文为互斥,Mutex 类叫做互斥锁。它还可用于进程间同步的同步基元
- Windows 操作系统中,Mutex 同步对象有两个状态:
- signaled:未被任何对象拥有;
- nonsignaled:被一个线程拥有;
Mutex 只能在获得锁的线程中,释放锁
6、跨线程访问Invoke
6.1 定义
在拥有此控件的基础窗口句柄的线程上执行指定的委托。
Invoke方法会顺着控件树向上搜索,直到找到创建控件的那个线程(通常是主线程),然后进入那个线程改变控件的外观,确保不发生线程冲突。
Invoke重载 | |
---|---|
Invoke(Action) | 在拥有此控件的基础窗口句柄的线程上执行指定的委托。 |
Invoke(Delegate) | 在拥有此控件的基础窗口句柄的线程上执行指定的委托。 |
Invoke(Delegate, Object[]) | 在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。 |
Invoke(Func) | 在拥有此控件的基础窗口句柄的线程上执行指定的委托。 |
偷懒可以使用属性设置为 来禁用 Control.CheckForIllegalCrossThreadCalls 异常 false。但是这样不安全。
6.2 属性 InvokeRequired
InvokeRequired 当前线程不是创建控件的线程时为true
6.3 用法
//不带参数
public delegate void MethodInvoker();
//带参数
public delegate void EventHandler(object sender, EventArgs e);
- 标准写法
private delegate void ModifyDelegate();
private delegate void ModifyDelegatePar(string s, bool t);
private void Invoke_Standard()
{
ModifyDelegate delge = new ModifyDelegate(Set);
this.Invoke(delge);
ModifyDelegatePar d2 = new ModifyDelegatePar(SetFirst);
this.Invoke(d2, new object[] { "ni" , true });
}
private void Set()
{
//do something
}
private void SetFirst(string s, bool t)
{
//do something
}
- 常用写法1[Lamda表达式]
//Action跟MethodInvoker差不多..都是无返回,无参数的委托
private void Invoke_LamdaMethod()
{
this.Invoke(new Action(() =>
{
this.label1.Text = "桃花坞里桃花庵%".ToString();
this.label2.Text = "桃花庵里桃花仙%".ToString();
this.label3.Text = "桃花仙人种桃树%".ToString();
this.label4.Text = "又摘桃花卖酒钱%".ToString();
}));
}
- 常用写法2[匿名委托]
扩展 beginInvoke
MethodInvoker 提供一个简单的委托,用于使用 void 参数列表调用方法。 在调用控件 Invoke 的方法或需要简单委托但不想自行定义时,可以使用此委托。