一、为什么要进行线程交互
在C#中,线程交互通常涉及到多个线程之间的数据共享和同步。.
一、全局变量
在C#中,全局变量是指在程序的任何地方都可以访问的变量。通常,全局变量是在类的外部定义的,或者在所有方法之外定义的。全局变量通常用于存储需要在多个方法或类之间共享的数据。
1. 在类外部定义
你可以在类定义之外定义全局变量,但这通常不推荐,因为这违背了面向对象编程的原则,即封装性。更好的做法是使用静态成员(静态变量)或单例模式来共享数据。
静态变量
静态变量属于类,而不是类的实例。这意味着你可以通过类名直接访问它,而不需要创建类的实例。
2. 使用单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。
3. 在类内部作为字段使用(非静态)
如果你想在多个方法之间共享数据,但又不想让这个数据成为公共的(即不想让其他类直接访问),可以将它定义为类的字段,并通过实例方法访问。虽然这不是真正的“全局”访问,因为它需要通过类的实例访问,但它仍然可以在多个方法间共享数据。
最佳实践和建议:
封装性:尽量使用封装性原则,通过属性、方法或单例模式来控制数据的访问。
可维护性:过度使用全局变量会使代码难以维护和理解。尽量减少全局变量的使用,特别是在大型项目中。考虑使用依赖注入等设计模式来管理依赖和共享数据。
线程安全:当涉及到多线程时,确保全局变量的访问是线程安全的。可以通过lock语句或者使用Concurrent集合来处理。
二、AutoResetEvent类
AutoResetEvent 是一种同步原语,它允许一个线程等待另一个线程发出信号。当一个线程调用 WaitOne() 方法时,它会被阻塞直到另一个线程调用 Set() 方法为止。每次 Set() 被调用后,AutoResetEvent 将自动重置为非信号状态,这意味着只能唤醒一个等待的线程。
三、 ManualResetEvent类
与 AutoResetEvent 类似,ManualResetEvent 允许一个或多个线程等待某个事件的发生。不同的是,ManualResetEvent 在 Set() 被调用之后不会自动重置,除非显式地调用了 Reset() 方法。
四、事件(Event)机制
通过事件和委托机制,可以在对象之间发送消息并通知订阅者。这种模式非常适合需要跨多个线程进行通信的情况,尤其是在图形用户界面(GUI)应用中,主线程通常用于UI更新,而子线程则处理后台任务。
五、Monitor
一、Monitor 类概述
Monitor 类位于 System.Threading 命名空间中,它允许你对对象进行锁定(即获取互斥锁),从而确保在同一时刻只有一个线程可以执行被锁定保护的代码块。
1. 基本锁机制
Monitor.Enter 和 Monitor.Exit 用于保护临界区,确保线程互斥访问共享资源。
与 lock 的关系:lock 关键字是 Monitor 的语法糖,编译后等价于 try-finally 块包裹的 Enter 和 Exit。
何时用 Monitor:需要超时控制或非阻塞尝试获取锁时(通过 TryEnter)。
2. 常用方法
Enter:尝试进入临界区并获取锁。
Exit:释放锁并退出临界区。
TryEnter:尝试进入临界区并在指定时间内等待锁。
Wait:释放当前线程持有的锁,并将该线程放入等待队列。
Pulse/PulseAll:通知等待队列中的一个或所有线程锁已释放,它们可以重新竞争锁。
3. 使用场景
保护共享资源:确保多个线程不会同时修改同一数据结构。
生产者-消费者模式:协调生产者和消费者之间的操作。
条件变量:实现基于条件的同步。
六、 Invoke和BeginInvoke
Invoke 和 BeginInvoke 在C#中主要用于跨线程操作UI控件,确保线程安全。
Invoke和BeginInvoke的作用
1、线程安全问题:在Windows GUI编程中,UI元素只能在创建它们的线程上访问。如果在一个非UI线程中直接操作UI控件,可能会导致 异常。Invoke和BeginInvoke方法用于在正确的线程上执行代码,避免这种问题。
2、同步与异步:
Invoke:同步调用,当前线程会阻塞,直到委托在目标线程(通常是UI线程)上执行完毕。适用于需要确保被调用的代码执行完成后,再继续执行后续逻辑的场景。
BeginInvoke:异步调用,当前线程不会阻塞,委托会立即被加入目标线程的消息队列,由目标线程异步执行。适用于不关心被调用代码的执行结果,或希望避免阻塞当前线程的场景。