在上一篇C#多线程之基础篇2中,我们主要讲述了确定线程的状态、线程优先级、前台线程和后台线程以及向线程传递参数的知识,在这一篇中我们将讲述如何使用C#的lock关键字锁定线程、使用Monitor锁定线程以及线程中的异常处理。

九、使用C#的lock关键字锁定线程

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,然后修改为如下代码:

 using System;
using System.Threading;
using static System.Console; namespace Recipe09
{
abstract class CounterBase
{
public abstract void Increment();
public abstract void Decrement();
} class Counter : CounterBase
{
public int Count { get; private set; } public override void Increment()
{
Count++;
} public override void Decrement()
{
Count--;
}
} class CounterWithLock : CounterBase
{
private readonly object syncRoot = new Object(); public int Count { get; private set; } public override void Increment()
{
lock (syncRoot)
{
Count++;
}
} public override void Decrement()
{
lock (syncRoot)
{
Count--;
}
}
} class Program
{
static void TestCounter(CounterBase c)
{
for (int i = ; i < ; i++)
{
c.Increment();
c.Decrement();
}
} static void Main(string[] args)
{
WriteLine("Incorrect counter");
var 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();
WriteLine($"Total count: {c1.Count}"); WriteLine("--------------------------"); WriteLine("Correct counter");
var 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();
WriteLine($"Total count: {c2.Count}");
}
}
}

3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

  在第65行代码处,我们创建了Counter类的一个对象,该类定义了一个简单的counter变量,该变量可以自增1和自减1。然后在第66~68行代码处,我们创建了三个线程,并利用lambda表达式将Counter对象传递给了“TestCounter”方法,这三个线程共享同一个counter变量,并且对这个变量进行自增和自减操作,这将导致结果的不正确。如果我们多次运行这个控制台程序,它将打印出不同的counter值,有可能是0,但大多数情况下不是。

  发生这种情况是因为Counter类是非线程安全的。我们假设第一个线程在第57行代码处执行完毕后,还没有执行第58行代码时,第二个线程也执行了第57行代码,这个时候counter的变量值自增了2次,然后,这两个线程同时执行了第58行处的代码,这会造成counter的变量只自减了1次,因此,造成了不正确的结果。

  为了确保不发生上述不正确的情况,我们必须保证在某一个线程访问counter变量时,另外所有的线程必须等待其执行完毕才能继续访问,我们可以使用lock关键字来完成这个功能。如果我们在某个线程中锁定一个对象,其他所有线程必须等到该线程解锁之后才能访问到这个对象,因此,可以避免上述情况的发生。但是要注意的是,使用这种方式会严重影响程序的性能。更好的方式我们将会在仙童同步中讲述。

十、使用Monitor锁定线程

  在这一小节中,我们将描述一个多线程编程中的常见的一个问题:死锁。我们首先创建一个死锁的示例,然后使用Monitor避免死锁的发生。

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写代码如下:

 using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe10
{
class Program
{
static void LockTooMuch(object lock1, object lock2)
{
lock (lock1)
{
Sleep();
lock (lock2)
{
}
}
} static void Main(string[] args)
{
object lock1 = new object();
object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2)
{
WriteLine("This will be a deadlock!");
Sleep();
lock (lock1)
{
WriteLine("Acquired a protected resource succesfully");
}
}
}
}
}

3、运行该控制台应用程序,运行效果如下图所示:

  在上述结果中我们可以看到程序发生了死锁,程序一直结束不了。

  在第10~19行代码处,我们定义了一个名为“LockTooMuch”的方法,在该方法中我们锁定了第一个对象lock1,等待1秒钟后,希望锁定第二个对象lock2。

  在第26行代码处,我们创建了一个新的线程来执行“LockTooMuch”方法,然后立即执行第28行代码。

  在第28~32行代码处,我们在主线程中锁定了对象lock2,然后等待1秒钟后,希望锁定第一个对象lock1。

  在创建的新线程中我们锁定了对象lock1,等待1秒钟,希望锁定对象lock2,而这个时候对象lock2已经被主线程锁定,所以新建线程会等待对象lock2被主线程解锁。然而,在主线程中,我们锁定了对象lock2,等待1秒钟,希望锁定对象lock1,而这个时候对象lock1已经被创建的线程锁定,所以主线程会等待对象lock1被创建的线程解锁。当发生这种情况的时候,死锁就发生了,所以我们的控制台应用程序目前无法正常结束。

4、要避免死锁的发生,我们可以使用“Monitor.TryEnter”方法来替换lock关键字,“Monitor.TryEnter”方法在请求不到资源时不会阻塞等待,可以设置超时时间,获取不到直接返回false。修改代码如下所示:

 using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe10
{
class Program
{
static void LockTooMuch(object lock1, object lock2)
{
lock (lock1)
{
Sleep();
lock (lock2)
{
}
}
} static void Main(string[] args)
{
object lock1 = new object();
object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2)
{
WriteLine("This will be a deadlock!");
Sleep();
//lock (lock1)
//{
// WriteLine("Acquired a protected resource succesfully");
//}
if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds()))
{
WriteLine("Acquired a protected resource succesfully");
}
else
{
WriteLine("Timeout acquiring a resource!");
}
}
}
}
}

5、运行该控制台应用程序,运行效果如下图所示:

  此时,我们的控制台应用程序就避免了死锁的发生。

十一、处理异常

  在这一小节中,我们讲述如何在线程中正确地处理异常。正确地将try/catch块放置在线程内部是非常重要的,因为在线程外部捕获线程内部的异常通常是不可能的。

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,修改代码如下所示:

 using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe11
{
class Program
{
static void BadFaultyThread()
{
WriteLine("Starting a faulty thread...");
Sleep(TimeSpan.FromSeconds());
throw new Exception("Boom!");
} static void FaultyThread()
{
try
{
WriteLine("Starting a faulty thread...");
Sleep(TimeSpan.FromSeconds());
throw new Exception("Boom!");
}
catch(Exception ex)
{
WriteLine($"Exception handled: {ex.Message}");
}
} static void Main(string[] args)
{
var t = new Thread(FaultyThread);
t.Start();
t.Join(); try
{
t = new Thread(BadFaultyThread);
t.Start();
}
catch (Exception ex)
{
WriteLine(ex.Message);
WriteLine("We won't get here!");
}
}
}
}

3、运行该控制台应用程序,运行效果如下图所示:

  在第10~15行代码处,我们定义了一个名为“BadFaultyThread”的方法,在该方法中抛出一个异常,并且没有使用try/catch块捕获该异常。

  在第17~29行代码处,我们定义了一个名为“FaultyThread”的方法,在该方法中也抛出一个异常,但是我们使用了try/catch块捕获了该异常。

  在第33~35行代码处,我们创建了一个线程,在该线程中执行了“FaultyThread”方法,我们可以看到在这个新创建的线程中,我们正确地捕获了在“FaultyThread”方法中抛出的异常。

  在第37~46行代码处,我们又新创建了一个线程,在该线程中执行了“BadFaultyThread”方法,并且在主线程中使用try/catch块来捕获在新创建的线程中抛出的异常,不幸的的是我们在主线程中无法捕获在新线程中抛出的异常。

  由此可以看到,在一个线程中捕获另一个线程中的异常通常是不可行的。

  至此,多线程(基础篇)我们就讲述到这儿,之后我们将讲述线程同步相关的知识,敬请期待!

  源码下载

C#多线程之基础篇3的更多相关文章

  1. C#多线程之基础篇2

    在上一篇C#多线程之基础篇1中,我们主要讲述了如何创建线程.中止线程.线程等待以及终止线程的相关知识,在本篇中我们继续讲述有关线程的一些知识. 五.确定线程的状态 在这一节中,我们将讲述如何查看一个线 ...

  2. C#多线程之基础篇1

    在多线程这一系列文章中,我们将讲述C#语言中多线程的相关知识,在多线程(基础篇)中我们将学习以下知识点: 创建线程 中止线程 线程等待 终止线程 确定线程的状态 线程优先级 前台线程和后台线程 向线程 ...

  3. Java多线程系列--“基础篇”11之 生产消费者问题

    概要 本章,会对“生产/消费者问题”进行讨论.涉及到的内容包括:1. 生产/消费者模型2. 生产/消费者实现 转载请注明出处:http://www.cnblogs.com/skywang12345/p ...

  4. Java多线程系列--“基础篇”04之 synchronized关键字

    概要 本章,会对synchronized关键字进行介绍.涉及到的内容包括:1. synchronized原理2. synchronized基本规则3. synchronized方法 和 synchro ...

  5. Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式

    概要 本章,我们学习“常用的实现多线程的2种方式”:Thread 和 Runnable.之所以说是常用的,是因为通过还可以通过java.util.concurrent包中的线程池来实现多线程.关于线程 ...

  6. Java多线程系列--“基础篇”03之 Thread中start()和run()的区别

    概要 Thread类包含start()和run()方法,它们的区别是什么?本章将对此作出解答.本章内容包括:start() 和 run()的区别说明start() 和 run()的区别示例start( ...

  7. Java多线程系列--“基础篇”05之 线程等待与唤醒

    概要 本章,会对线程等待/唤醒方法进行介绍.涉及到的内容包括:1. wait(), notify(), notifyAll()等方法介绍2. wait()和notify()3. wait(long t ...

  8. Java多线程系列--“基础篇”06之 线程让步

    概要 本章,会对Thread中的线程让步方法yield()进行介绍.涉及到的内容包括:1. yield()介绍2. yield()示例3. yield() 与 wait()的比较 转载请注明出处:ht ...

  9. Java多线程系列--“基础篇”07之 线程休眠

    概要 本章,会对Thread中sleep()方法进行介绍.涉及到的内容包括:1. sleep()介绍2. sleep()示例3. sleep() 与 wait()的比较 转载请注明出处:http:// ...

随机推荐

  1. 对Castle Windsor的Resolve方法的解析时new对象的探讨

    依赖注入框架Castle Windsor从容器里解析一个实例时(也就是调用Resolve方法),是通过调用待解析对象的构造函数new一个对象并返回,那么问题是:它是调用哪个构造函数呢? 无参的构造函数 ...

  2. 再谈CAAnimation动画

    CAAnimaton动画分为CABasicAnimation & CAKeyframeAnimation CABasicAnimation动画, 顾名思义就是最基本的动画, 老规矩先上代码: ...

  3. C#多线程之线程池篇3

    在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...

  4. P2V之后的磁盘扩容新思路

    背景: 原先的物理机环境多是若干块物理磁盘经过RAID卡进行了RAID5之后的虚拟磁盘组,这样我们在操作系统内看到的也就是一块完整的磁盘.我们会在上面进行分区,然后格式化后以便使用. Figure 1 ...

  5. c# 基础 object ,new操作符,类型转换

    参考页面: http://www.yuanjiaocheng.net/webapi/config-webapi.html http://www.yuanjiaocheng.net/webapi/web ...

  6. 【JS基础】对象

    delete 可以删除对象属性及变量 function fun(){ this.name = 'mm'; } var obj = new fun(); console.log(obj.name);// ...

  7. 【SAP业务模式】之ICS(五):定价配置

    本篇博文讲述ICS业务中的定价配置. 1.定义销售订单类型 目录:SPRO-销售与分销-销售-销售凭证-销售凭证抬头-定义销售凭证类型 事务代码:VOV8 2.定义销售订单类型 目录:SPRO-销售与 ...

  8. 监控 SQL Server (2005/2008) 的运行状况

    Microsoft SQL Server 2005 提供了一些工具来监控数据库.方法之一是动态管理视图.动态管理视图 (DMV) 和动态管理函数 (DMF) 返回的服务器状态信息可用于监控服务器实例的 ...

  9. Linux测试环境搭建的学习建议

    随着Linux应用的扩展许多朋友开始接触Linux,根据学习Windwos的经验往往有一些茫然的感觉:不知从何处开始学起.这里介绍学习Linux测试环境搭建的一些建议. 一.Linux测试环境搭建从基 ...

  10. IOS开发之—— 在AFN基础上进行的网络请求的封装

    网络请求的思路:如果请求成功的话AFN的responseObject就是解析好的. 1发送网络请求:get/post/或者别的 带上URL,需要传的参数 2判断后台网络状态码有没有请求成功: 3 请求 ...