C#多线程之线程池篇3
在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识。在这一篇中,我们主要学习如何使用等待句柄和超时、使用计时器和使用BackgroundWorker组件的相关知识。
五、使用等待句柄和超时
在这一小节中,我们将学习如何在线程池中实现超时和正确地实现等待。具体操作步骤如下:
1、使用Visual Studio 2015创建一个新的控制台应用程序。
2、双击打开“Program.cs”文件,编写代码如下所示:
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe05
{
class Program
{
// CancellationTokenSource:通知System.Threading.CancellationToken,告知其应被取消。
static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
{
if (isTimedOut)
{
// 传达取消请求。
cts.Cancel();
WriteLine("Worker operation timed out and was canceled.");
}
else
{
WriteLine("Worker operation succeeded.");
}
} // CancellationToken:传播有关应取消操作的通知。
// ManualResetEvent:通知一个或多个正在等待的线程已发生事件。
static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
{
for (int i = ; i < ; i++)
{
// 获取是否已请求取消此标记。如果已请求取消此标记,则为 true;否则为 false。
if (token.IsCancellationRequested)
{
return;
}
Sleep(TimeSpan.FromSeconds());
}
// 将事件状态设置为终止状态,允许一个或多个等待线程继续。
evt.Set();
} static void RunOperations(TimeSpan workerOperationTimeout)
{
using (var evt = new ManualResetEvent(false))
using (var cts = new CancellationTokenSource())
{
// 注册一个等待System.Threading.WaitHandle的委托,并指定一个System.TimeSpan值来表示超时时间。
// 第一个参数:要注册的System.Threading.WaitHandle。使用System.Threading.WaitHandle而非 System.Threading.Mutex。
// 第二个参数:waitObject参数终止时调用的System.Threading.WaitOrTimerCallback 委托。
// 第三个参数:传递给委托的对象。
// 第四个参数:System.TimeSpan表示的超时时间。如果timeout为0(零),则函数将测试对象的状态并立即返回。如果timeout为 -1,则函数的超时间隔永远不过期。
// 第五个参数:如果为true,表示在调用了委托后,线程将不再在waitObject参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。
// 返回值:封装本机句柄的System.Threading.RegisteredWaitHandle。
var worker = ThreadPool.RegisterWaitForSingleObject(evt, (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut), null, workerOperationTimeout, true); WriteLine("Starting long running operation...");
// ThreadPool.QueueUserWorkItem:将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。
// cts.Token:获取与此System.Threading.CancellationTokenSource关联的System.Threading.CancellationToken。
ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt)); Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds())); // 取消由System.Threading.ThreadPool.RegisterWaitForSingleObject方法发出的已注册等待操作。
worker.Unregister(evt);
}
} static void Main(string[] args)
{
// 实现超时
RunOperations(TimeSpan.FromSeconds());
// 实现等待
RunOperations(TimeSpan.FromSeconds());
}
}
}
3、运行该控制台应用程序,运行效果如下图所示:
线程池还有另一个有用的方法:ThreadPool.RegisterWaitForSingleObject,该方法允许我们将回调方法放入线程池的队列中,当所提供的等待句柄发送信号或者超时发生时,该回调方法即被执行。这允许我们对线程池中的操作实现超时。
在第71行代码处,我们在主线程中调用了“RunOperations”方法,并给它的workerOperationTimeout参数传递了数值5,表示超时时间为5秒。
在第54行代码处,我们调用了ThreadPool的“RegisterWaitForSingleObject”静态方法,并指定了回调方法所要执行的操作是“WorkerOperationWait”方法,超时时间是5秒。
在第59行代码处,我们调用ThreadPool的“QueueUserWorkItem”静态方法来执行“WorkerOperation”方法,而该方法所消耗的时间为6秒,在这六秒中内已经在线程池中发送了超时,所以会执行第13~18行和第32~35行处的代码。
在第73行代码处,我们传递了数值7给“RunOperations”方法,设置线程池的超时时间为7秒,因为“WorkerOperation”方法的执行时间为6秒,所以在这种情况下没有发生超时,成功执行完毕“WorkerOperation”方法。
六、使用计时器
在这一小节中,我们将学习如何使用System.Threading.Timer对象在线程池中定期地调用一个异步操作。具体操作步骤如下所示:
1、使用Visual Studio 2015创建一个新的控制台应用程序。
2、双击打开“Program.cs”文件,编写代码如下所示:
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe06
{
class Program
{
static Timer timer; static void TimerOperation(DateTime start)
{
TimeSpan elapsed = DateTime.Now - start;
WriteLine($"{elapsed.Seconds} seconds from {start}. Timer thread pool thread id: {CurrentThread.ManagedThreadId}");
} static void Main(string[] args)
{
WriteLine("Press 'Enter' to stop the timer...");
DateTime start = DateTime.Now;
// 初始化Timer类的新实例,使用System.TimeSpan值来度量时间间隔。
// 第一个参数:一个System.Threading.TimerCallback委托,表示要执行的方法。
// 第二个参数:一个包含回调方法要使用的信息的对象,或者为null。
// 第三个参数:System.TimeSpan,表示在callback参数调用它的方法之前延迟的时间量。指定-1毫秒以防止启动计时器。指定零(0)可立即启动计时器。
// 第四个参数:在调用callback所引用的方法之间的时间间隔。指定-1毫秒可以禁用定期终止。
timer = new Timer(_ => TimerOperation(start), null, TimeSpan.FromSeconds(), TimeSpan.FromSeconds());
try
{
Sleep(TimeSpan.FromSeconds());
// 更改计时器的启动时间和方法调用之间的时间间隔,使用System.TimeSpan值度量时间间隔。
// 第一个参数:一个System.TimeSpan,表示在调用构造System.Threading.Timer时指定的回调方法之前的延迟时间量。指定负-1毫秒以防止计时器重新启动。指定零(0)可立即重新启动计时器。
// 第二个参数:在构造System.Threading.Timer时指定的回调方法调用之间的时间间隔。指定-1毫秒可以禁用定期终止。
timer.Change(TimeSpan.FromSeconds(), TimeSpan.FromSeconds());
ReadLine();
}
finally
{
timer.Dispose();
}
}
}
}
3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:
首先,我们创建了一个Timer实例,它的构造方法的第一个参数是一个lambda表达式,表示要在线程池中执行的代码,在该表达式中我们调用了“TimerOperation”方法,并给它提供了一个开始时间值。由于我们没有使用state对象,因此我们给Timer的构造方法的第二个参数传递了null。第三个参数表示第一次执行“TimerOperation”所要花费的时间为1秒钟。第四个参数表示每次调用“TimerOperation”之间的时间间隔为2秒钟。
在主线程阻塞6秒钟之后,我们调用了Timer实例的“Change”方法,更改了每次调用“TimerOperation”之间的时间间隔为4秒钟。
最后,我们等待输入“Enter”键来结束应用程序。
七、使用BackgroundWorker组件
在这一小节中,我们学习另外一种异步编程的方式:BackgroundWorker组件。在这个组件的帮助下,我们可以通过一系列事件和事件处理方法组织我们的异步代码。具体操作步骤如下所示:
1、使用Visual Studio 2015创建一个新的控制台应用程序。
2、双击打开“Program.cs”文件,编写代码如下所示:
using System;
using System.ComponentModel;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe07
{
class Program
{
static void WorkerDoWork(object sender, DoWorkEventArgs e)
{
WriteLine($"DoWork thread pool thread id: {CurrentThread.ManagedThreadId}");
var bw = (BackgroundWorker)sender;
for (int i = ; i <= ; i++)
{
// 获取一个值,指示应用程序是否已请求取消后台操作。
// 如果应用程序已请求取消后台操作,则为 true;否则为 false。默认值为 false。
if (bw.CancellationPending)
{
e.Cancel = true;
return;
} if (i % == )
{
// 引发 System.ComponentModel.BackgroundWorker.ProgressChanged 事件。
// 参数:已完成的后台操作所占的百分比,范围从 0% 到 100%。
bw.ReportProgress(i);
} Sleep(TimeSpan.FromSeconds(0.1));
} // 获取或设置表示异步操作结果的值。
e.Result = ;
} static void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
// e.ProgressPercentage:获取异步任务的进度百分比。
WriteLine($"{e.ProgressPercentage}% completed. Progress thread pool thread id: {CurrentThread.ManagedThreadId}");
} static void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
WriteLine($"Completed thread pool thread id: {CurrentThread.ManagedThreadId}");
// e.Error:获取一个值,该值指示异步操作期间发生的错误。
if (e.Error != null)
{
// 打印出异步操作期间发生的错误信息。
WriteLine($"Exception {e.Error.Message} has occured.");
}
else if (e.Cancelled) // 获取一个值,该值指示异步操作是否已被取消。
{
WriteLine($"Operation has been canceled.");
}
else
{
// e.Result:获取表示异步操作结果的值。
WriteLine($"The answer is: {e.Result}");
}
} static void Main(string[] args)
{
// 初始化System.ComponentModel.BackgroundWorker类的新实例。该类在单独的线程上执行操作。
var bw = new BackgroundWorker();
// 获取或设置一个值,该值指示System.ComponentModel.BackgroundWorker能否报告进度更新。
// 如果System.ComponentModel.BackgroundWorker支持进度更新,则为true;否则为false。默认值为false。
bw.WorkerReportsProgress = true;
// 获取或设置一个值,该值指示System.ComponentModel.BackgroundWorker是否支持异步取消。
// 如果System.ComponentModel.BackgroundWorker支持取消,则为true;否则为false。默认值为false。
bw.WorkerSupportsCancellation = true; // 调用System.ComponentModel.BackgroundWorker.RunWorkerAsync时发生。
bw.DoWork += WorkerDoWork;
// 调用System.ComponentModel.BackgroundWorker.ReportProgress(System.Int32)时发生。
bw.ProgressChanged += WorkerProgressChanged;
// 当后台操作已完成、被取消或引发异常时发生。
bw.RunWorkerCompleted += WorkerCompleted; // 开始执行后台操作。
bw.RunWorkerAsync(); WriteLine("Press C to cancel work"); do
{
// 获取用户按下的下一个字符或功能键。按下的键可以选择显示在控制台窗口中。
// 确定是否在控制台窗口中显示按下的键。如果为 true,则不显示按下的键;否则为 false。
if (ReadKey(true).KeyChar == 'C')
{
// 请求取消挂起的后台操作。
bw.CancelAsync();
}
}
// 获取一个值,指示System.ComponentModel.BackgroundWorker是否正在运行异步操作。
// 如果System.ComponentModel.BackgroundWorker正在运行异步操作,则为true;否则为false。
while (bw.IsBusy);
}
}
}
3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:
在第68行代码处,我们创建了一个BackgroundWorker组件的实例,并且在第71行代码和第74行代码处明确地说明该实例支持进度更新和异步取消操作。
在第77行代码、第79行代码和第81行代码处,我们给该实例挂载了三个事件处理方法。每当DoWork、ProgressChanged和RunWorkerCompleted事件发生时,都会执行相应的“WorkerDoWork方法”、“WorkerProgressChanged”方法和“WorkerCompleted”方法。
其他代码请参考注释。
C#多线程之线程池篇3的更多相关文章
- C#多线程之线程池篇2
在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...
- C#多线程之线程池篇1
在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...
- JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor
JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...
- Qt中的多线程与线程池浅析+实例
1. Qt中的多线程与线程池 今天学习了Qt中的多线程和线程池,特写这篇博客来记录一下 2. 多线程 2.1 线程类 QThread Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一 ...
- Java 多线程:线程池
Java 多线程:线程池 作者:Grey 原文地址: 博客园:Java 多线程:线程池 CSDN:Java 多线程:线程池 工作原理 线程池内部是通过队列结合线程实现的,当我们利用线程池执行任务时: ...
- C#多线程之线程同步篇3
在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...
- C#多线程之线程同步篇2
在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...
- 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法
[源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...
- ExecutorService 建立一个多线程的线程池的步骤
ExecutorService 建立一个多线程的线程池的步骤: 线程池的作用: 线程池功能是限制在系统中运行的线程数. 依据系统的环境情况,能够自己主动或手动设置线程数量.达到执行的最佳效果:少了浪费 ...
随机推荐
- 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...
- 懒加载session 无法打开 no session or session was closed 解决办法(完美解决)
首先说明一下,hibernate的延迟加载特性(lazy).所谓的延迟加载就是当真正需要查询数据时才执行数据加载操作.因为hibernate当中支持实体对象,外键会与实体对象关联起来.如 ...
- PowerDesigner-VBSrcipt-自动设置主键,外键名等(SQL Server)
在PowerDesigner中的设计SQL Server 数据表时,要求通过vbScript脚本实现下面的功能: 主键:pk_TableName 外键:fk_TableName_ForeignKeyC ...
- PHP与API讲解(一)
了解API: 在使用与创建自己的API之前我们需要先了解什么是API! API代表应用程序编程接口,而接口指的是一个特定的服务.一个应用程序或者其他程序的公共模块. 理解SOA(面向服务的架构):SO ...
- JavaScript基础知识总结(三)
JavaScript语法 七.循环语句 1.while 语法: while (exp) { //statements; } 说明:while (变量<=结束值) { 需执行的代码 } 例子: / ...
- Node.js使用PM2的集群将变得更加容易
介绍 众所周知,Node.js运行在Chrome的JavaScript运行时平台上,我们把该平台优雅地称之为V8引擎.不论是V8引擎,还是之后的Node.js,都是以单线程的方式运行的,因此,在多核心 ...
- 自定义控件之 圆形 / 圆角 ImageView
一.问题在哪里? 问题来源于app开发中一个很常见的场景——用户头像要展示成圆的: 二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在 ...
- 让你从零开始学会写爬虫的5个教程(Python)
写爬虫总是非常吸引IT学习者,毕竟光听起来就很酷炫极客,我也知道很多人学完基础知识之后,第一个项目开发就是自己写一个爬虫玩玩. 其实懂了之后,写个爬虫脚本是很简单的,但是对于新手来说却并不是那么容易. ...
- 解决maven下载jar慢的问题(如何更换Maven下载源)
修改 配置文件 maven 安装 路径 F:\apache-maven-3.3.9\conf 修改 settings.xml 在 <mirrors> <!-- mirror | Sp ...
- 浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因
本文出处:http://www.cnblogs.com/wy123/p/6189100.html 标题有点拗口,来源于一个开发人员遇到的实际问题 先抛出问题:一个查询没有明确指定排序方式,那么,第二次 ...