介绍
在使用多线程模型进行编程时,经常遇到的问题之一是,当我们关闭前台的UI线程时,后台的辅助线程仍然处于活动状态,从而导致整个应用程序无法正常退出。这时我们需要一种较安全的方式来结束后台线程的运行,这样我们可以随时结束后台线程的运行,并且在线程结束时进行相应的资源清理工作(例如将内存数据写入硬盘)。.net框架提供了一些工具来实现该功能。
目录
IsBackground属性
Abort方法
轮循方式
取消阻塞的线程
IsBackgound属性
Thread类提供了IsBackground属性,当线程的IsBackground属性被设置为true时,表示此线程为后台工作线程。当一个应用程序结束时,它的所有后台线程会自动的被结束执行。如果你有一个后台线程侦听Socket连接,并且正在被阻塞,那么这时候通过设置线程的IsBackground属性为True,使它自动随应用程序的结束而结束是比较合适的。但在这种情况下,线程会静悄悄的结束,它不会引发任何异常,你的线程没有机会执行一些需要的清理代码。例如,内存中的数据可能会来不及写入磁盘,从而造成丢失数据。
Abort方法
可以调用Thread类的Abort方法来强制终制线程。上调用此方法时,线程上引发ThreadAbortException,并导至线程终结,通过捕获该异常,可以执行一些资源清理代码。但这种模式也有一些问题,主要是难以知道线程上的代码执行到什么地方,所有相应的资源清理代码也难以编写。总的来说这是一种比较粗暴的终止线程执行的方法,通常来说是不推荐使用的。
轮循方式
如果后台线程将执行一个很长的计算,那么可以将计算隔成若干小段,并经常检查是否需要取消线程。.NET框架提供了CancellationTokenSource类来作为线程取消的统一模式。例如:
复制代码 代码如下:
public class Example
{
public static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
var thread = new Thread(ThreadWork);
thread.Start(cts.Token);
while (true)
{
if(Console.ReadKey().KeyChar == 'c')
{
Console.WriteLine("请求取消线程的执行");
cts.Cancel();
break;
}
}
Console.ReadLine();
}
private static void ThreadWork(object state)
{
CancellationToken cancellationToken = (CancellationToken)state;
while (true)
{
// 检查是否取消
if(cancellationToken.IsCancellationRequested)
{
Console.WriteLine("线程已经取消了");
Console.WriteLine("线程的资源已经清理完成。");
break;
}
// 模拟工作
Thread.SpinWait(500000);
Console.WriteLine("我还在工作。");
}
}
}
取消阻塞的线程
上面的示例中,后台线程会长时间进行计算,但更多的时候,线程会由于等待某个事件,从而进入阻塞状态。这个时候,实际上线程已经不再执行状态了,很明显,它没有机会去检查取消标志。 那么,该如何解决这个问题呢?CancellationToken的WaitHandle属性提供了解答。WaitHandle类有一个静态方法WaitAny,它可以同时等待多个事件,当多个事件中的任意一个有效时,线程都会从阻塞状态中返回。可以根据WaitAny方法的返回值来判断发生了什么事件,从而相应的执行代码。例子:
复制代码 代码如下:
public class Example
{
private static int Value;
public static void Main()
{
var autoResetEvent = new AutoResetEvent(false);
var cts = new CancellationTokenSource();
var state = new { ValueAvailableEvent = autoResetEvent, CancellationToken = cts.Token };
var threadConsumer = new Thread(ConsumerThreadWork);
var threadProducter = new Thread(ProducterThreadWork);
threadConsumer.Start(state);
threadProducter.Start(state);
while (true)
{
if (Console.ReadKey().KeyChar == 'c')
{
Console.WriteLine("请求取消线程的执行");
cts.Cancel();
break;
}
}
Console.ReadLine();
}
public static void ProducterThreadWork(dynamic state)
{
var valueAvailableEvent = (AutoResetEvent)state.ValueAvailableEvent;
var cancellationToken = (CancellationToken)state.CancellationToken;
var rand = new Random();
while (!cancellationToken.IsCancellationRequested)
{
Value = rand.Next();
Console.WriteLine("\r\n产生一个值{0}", Value);
valueAvailableEvent.Set();
Thread.Sleep(500);
}
Console.WriteLine("生产者线程被取消。");
}
public static void ConsumerThreadWork(dynamic state)
{
var valueAvailableEvent = (AutoResetEvent)state.ValueAvailableEvent;
var cancellationToken = (CancellationToken)state.CancellationToken;
var events = new[] { valueAvailableEvent, cancellationToken.WaitHandle };
while (true)
{
var eventIndex = WaitHandle.WaitAny(events);
// 处理数据
if (eventIndex == 0)
{
Console.WriteLine("处理值{0}。", Value);
}
// 处理取消事件
else if (eventIndex == 1)
{
Console.WriteLine("消费者线程被取消。");
break;
}
}
}
}
在上面的例子中,有三个线程,分别是UI线程,生产者线程和消费者线程。其中生产者线程每隔一秒产生一个有效数值,并将数据保存到Value字段中,而消费者线程等待值的产生,这个等待的过程是阻塞的。消费都线程通过WaitHandle.WaitAny方法来同时等待值有效事件或者取消事件,当任意一个事件有效时,线程都将继续,并且通过返回的值来判断发生的事件,并作相应的处理。
总结
多线程模型中的线程取消问题还是比较复杂的。Thread.IsBackground属性提供了在前台线程结束后自动结束线程的方法。Thread.Abort方法提供了一种“粗暴”的结束线程的方法。CancellationTokenSource类则是线程取消的标准模式,我们应当更多的使用这种模式。文章写的不多,基本是字数不够,代码来凑,大家伙将就的看看吧。
在使用多线程模型进行编程时,经常遇到的问题之一是,当我们关闭前台的UI线程时,后台的辅助线程仍然处于活动状态,从而导致整个应用程序无法正常退出。这时我们需要一种较安全的方式来结束后台线程的运行,这样我们可以随时结束后台线程的运行,并且在线程结束时进行相应的资源清理工作(例如将内存数据写入硬盘)。.net框架提供了一些工具来实现该功能。
目录
IsBackground属性
Abort方法
轮循方式
取消阻塞的线程
IsBackgound属性
Thread类提供了IsBackground属性,当线程的IsBackground属性被设置为true时,表示此线程为后台工作线程。当一个应用程序结束时,它的所有后台线程会自动的被结束执行。如果你有一个后台线程侦听Socket连接,并且正在被阻塞,那么这时候通过设置线程的IsBackground属性为True,使它自动随应用程序的结束而结束是比较合适的。但在这种情况下,线程会静悄悄的结束,它不会引发任何异常,你的线程没有机会执行一些需要的清理代码。例如,内存中的数据可能会来不及写入磁盘,从而造成丢失数据。
Abort方法
可以调用Thread类的Abort方法来强制终制线程。上调用此方法时,线程上引发ThreadAbortException,并导至线程终结,通过捕获该异常,可以执行一些资源清理代码。但这种模式也有一些问题,主要是难以知道线程上的代码执行到什么地方,所有相应的资源清理代码也难以编写。总的来说这是一种比较粗暴的终止线程执行的方法,通常来说是不推荐使用的。
轮循方式
如果后台线程将执行一个很长的计算,那么可以将计算隔成若干小段,并经常检查是否需要取消线程。.NET框架提供了CancellationTokenSource类来作为线程取消的统一模式。例如:
复制代码 代码如下:
public class Example
{
public static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
var thread = new Thread(ThreadWork);
thread.Start(cts.Token);
while (true)
{
if(Console.ReadKey().KeyChar == 'c')
{
Console.WriteLine("请求取消线程的执行");
cts.Cancel();
break;
}
}
Console.ReadLine();
}
private static void ThreadWork(object state)
{
CancellationToken cancellationToken = (CancellationToken)state;
while (true)
{
// 检查是否取消
if(cancellationToken.IsCancellationRequested)
{
Console.WriteLine("线程已经取消了");
Console.WriteLine("线程的资源已经清理完成。");
break;
}
// 模拟工作
Thread.SpinWait(500000);
Console.WriteLine("我还在工作。");
}
}
}
取消阻塞的线程
上面的示例中,后台线程会长时间进行计算,但更多的时候,线程会由于等待某个事件,从而进入阻塞状态。这个时候,实际上线程已经不再执行状态了,很明显,它没有机会去检查取消标志。 那么,该如何解决这个问题呢?CancellationToken的WaitHandle属性提供了解答。WaitHandle类有一个静态方法WaitAny,它可以同时等待多个事件,当多个事件中的任意一个有效时,线程都会从阻塞状态中返回。可以根据WaitAny方法的返回值来判断发生了什么事件,从而相应的执行代码。例子:
复制代码 代码如下:
public class Example
{
private static int Value;
public static void Main()
{
var autoResetEvent = new AutoResetEvent(false);
var cts = new CancellationTokenSource();
var state = new { ValueAvailableEvent = autoResetEvent, CancellationToken = cts.Token };
var threadConsumer = new Thread(ConsumerThreadWork);
var threadProducter = new Thread(ProducterThreadWork);
threadConsumer.Start(state);
threadProducter.Start(state);
while (true)
{
if (Console.ReadKey().KeyChar == 'c')
{
Console.WriteLine("请求取消线程的执行");
cts.Cancel();
break;
}
}
Console.ReadLine();
}
public static void ProducterThreadWork(dynamic state)
{
var valueAvailableEvent = (AutoResetEvent)state.ValueAvailableEvent;
var cancellationToken = (CancellationToken)state.CancellationToken;
var rand = new Random();
while (!cancellationToken.IsCancellationRequested)
{
Value = rand.Next();
Console.WriteLine("\r\n产生一个值{0}", Value);
valueAvailableEvent.Set();
Thread.Sleep(500);
}
Console.WriteLine("生产者线程被取消。");
}
public static void ConsumerThreadWork(dynamic state)
{
var valueAvailableEvent = (AutoResetEvent)state.ValueAvailableEvent;
var cancellationToken = (CancellationToken)state.CancellationToken;
var events = new[] { valueAvailableEvent, cancellationToken.WaitHandle };
while (true)
{
var eventIndex = WaitHandle.WaitAny(events);
// 处理数据
if (eventIndex == 0)
{
Console.WriteLine("处理值{0}。", Value);
}
// 处理取消事件
else if (eventIndex == 1)
{
Console.WriteLine("消费者线程被取消。");
break;
}
}
}
}
在上面的例子中,有三个线程,分别是UI线程,生产者线程和消费者线程。其中生产者线程每隔一秒产生一个有效数值,并将数据保存到Value字段中,而消费者线程等待值的产生,这个等待的过程是阻塞的。消费都线程通过WaitHandle.WaitAny方法来同时等待值有效事件或者取消事件,当任意一个事件有效时,线程都将继续,并且通过返回的值来判断发生的事件,并作相应的处理。
总结
多线程模型中的线程取消问题还是比较复杂的。Thread.IsBackground属性提供了在前台线程结束后自动结束线程的方法。Thread.Abort方法提供了一种“粗暴”的结束线程的方法。CancellationTokenSource类则是线程取消的标准模式,我们应当更多的使用这种模式。文章写的不多,基本是字数不够,代码来凑,大家伙将就的看看吧。
广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
暂无评论...
更新日志
2024年11月29日
2024年11月29日
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]