以下各节描述了在多线程应用程序中可以用来同步资源访问的功能和类。 在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。 然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。 对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。对于其他所有数据类型和非线程安全的资源,只有使用本主题中的结构才能安全地执行多线程处理。 有关多线程编程的背景信息,请参见: Managed Threading Basics Using Threads and Threading Managed Threading Best Practices 锁和 SyncLock 关键字 lock (C#) 和 SyncLock (Visual Basic) 语句可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。 lock 或 SyncLock 语句有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块。例如: C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class TestThreading { private System.Object lockThis = new System.Object(); public void Process() { lock (lockThis) { // Access thread-sensitive resources. } } } |
提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上面的示例中,锁的范围限定为此函数,因为函数外不存在任何对对象 lockThis 的引用。如果确实存在此类引用,锁的范围将扩展到该对象。严格地说,提供的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。 通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行时 (CLR)“暂留”。这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。 有关 lock 和 SyncLock 语句的更多信息,请参见以下主题: “锁定”语句(C# 参考) SyncLock 语句 监视器 监视器 与 lock 和 SyncLock 关键字类似,监视器防止多个线程同时执行代码块。 Enter 方法允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用 Exit。这与使用 lock 关键字一样。例如: C#
1 2 3 4 |
lock (x) { DoSomething(); } |
这等效于: C#
1 2 3 4 5 6 7 8 9 10 |
System.Object obj = (System.Object)x; System.Threading.Monitor.Enter(obj); try { DoSomething(); } finally { System.Threading.Monitor.Exit(obj); } |
使用 lock (C#) 或 SyncLock (Visual Basic) 关键字通常比直接使用 Monitor […]
View Detailslock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 下面的示例包含一个 lock 语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Account { decimal balance; private Object thisLock = new Object(); public void Withdraw(decimal amount) { lock (thisLock) { if (amount > balance) { throw new Exception("Insufficient funds"); } balance -= amount; } } } |
有关更多信息,请参见 线程同步(C# 和 Visual Basic)。 备注 lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。 线程处理(C# 和 Visual Basic) 这节讨论了线程处理。 lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。 通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则: 如果实例可以被公共访问,将出现 lock (this) 问题。 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。 最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。 在 lock 语句的正文不能使用 等待 关键字。 示例 下面演示在 C# 中使用未锁定的线程的简单示例。 C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//using System.Threading; class ThreadTest { public void RunMe() { Console.WriteLine("RunMe called"); } static void Main() { ThreadTest b = new ThreadTest(); Thread t = new Thread(b.RunMe); t.Start(); } } // Output: RunMe called |
示例 下例使用线程和 lock。 只要 lock 语句存在,语句块就是临界区并且 balance 永远不会是负数。 C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// using System.Threading; class Account { private Object thisLock = new Object(); int balance; Random r = new Random(); public Account(int initial) { balance = initial; } int Withdraw(int amount) { // This condition never is true unless the lock statement // is commented out. if (balance < 0) { throw new Exception("Negative Balance"); } // Comment out the next line to see the effect of leaving out // the lock keyword. lock (thisLock) { if (balance >= amount) { Console.WriteLine("Balance before Withdrawal : " + balance); Console.WriteLine("Amount to Withdraw : -" + amount); balance = balance - amount; Console.WriteLine("Balance after Withdrawal : " + balance); return amount; } else { return 0; // transaction rejected } } } public void DoTransactions() { for (int i = 0; i < 100; i++) { Withdraw(r.Next(1, 100)); } } } class Test { static void Main() { Thread[] threads = new Thread[10]; Account acc = new Account(1000); for (int i = 0; i < 10; i++) { Thread t = new Thread(new ThreadStart(acc.DoTransactions)); threads[i] = t; } for (int i = 0; i < 10; i++) { threads[i].Start(); } } } |
from:https://msdn.microsoft.com/zh-cn/library/c5kehkcz.aspx
View Details