6搞懂线程池(二)
抱歉各位多线程专栏托更这么久,这篇文章我们继续讲线程池的相关知识,其中将涉及到如下知识:
- 取消异步操作
- 等待事件处理器及超时
- 计时器
- BackgroundWorker
零、取消异步操作
这一小节将引入两个类 CancellationTokenSource 和 CancellationToken 。这两个类是在 .NET 4.0 中被引入的,因此如果需要使用这两个类我们必须在 .NET 4.0 及其以上版本中使用,目前是取消异步操作的标准。下面我们通过厨师做饭,中途撤销订单的例子来看一下这两个类具体该怎么用。
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace NoSix
{
class Program
{
static void Main(string[] args)
{
using(var cts=new CancellationTokenSource())
{
CancellationToken token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => Cookie(token));
Sleep(2000);
cts.Cancel();
}
Read();
}
static void Cookie(CancellationToken token)
{
WriteLine("开始做饭.......");
for (int i = 0; i < 5; i++)
{
if (token.IsCancellationRequested)
{
WriteLine("取消做饭");
return;
}
Sleep(2000);
}
WriteLine("我做完饭了");
}
}
}
在上面的代码中我们在 Cookie 方法中通过轮询的方式来检查 CancellationToken.IsCancellationRequested 属性。如果该属性为 true ,则说明操作需要被取消,我们必须放弃该操作。下面我们将 Cookie 方法修改一下,用另一种方式来实现取消操作
static void Cookie(CancellationToken token)
{
try
{
WriteLine("开始做饭.......");
for (int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested();
Sleep(2000);
}
WriteLine("我做完饭了");
}
catch(OperationCanceledException)
{
WriteLine("取消做饭");
}
}
这种方法我们抛出一个 OperationCancelledException 异常。这允许我们在线程池之外控制取消执行过程。需要取消操作时通过操作之外的代码来处理。下面我们再来修改一下 Cookie 方法,用第三种方法来是先取消操作。
static void Cookie(CancellationToken token)
{
WriteLine("开始做饭.......");
bool cancellationFlag = false;
token.Register(() => cancellationFlag = true);
for (int i = 0; i < 5; i++)
{
if (cancellationFlag)
{
WriteLine("取消做饭");
return;
}
Sleep(2000);
}
WriteLine("我做完饭了");
}
第三种方式是注册一个回调函数。操作被取消时线程池将调用该回调函数。.NET 可以链式的传递一个取消逻辑到另一个异步操作中。
一、等待事件处理器及超时
在线程池中存在一个非常棒的方法 RegisterWaitForSingleObject 。它允许我们把回调函数放入线程池,每当等待事件处理器收到信号或者等待超时时将执行这个回调函数。下面的代码通过模拟初始等待下单做饭,到了下班时间(超时)后就停止接单。
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace RegisterWaitForSingleObject
{
class Program
{
static void Main(string[] args)
{
Cookie(TimeSpan.FromSeconds(5));
Cookie(TimeSpan.FromSeconds(7));
Read();
}
static void Cookie(TimeSpan timeSpan)
{
using (var evt = new ManualResetEvent(false))
using (var cts = new CancellationTokenSource())
{
WriteLine("等待做饭");
var cookie = ThreadPool.RegisterWaitForSingleObject(evt, (state, isTimeOut) => CookieWait(cts, isTimeOut), null, timeSpan, true);
ThreadPool.QueueUserWorkItem(_ => WorkOperation(cts.Token, evt));
Sleep(2000);
cookie.Unregister(evt);
}
}
private static void WorkOperation(CancellationToken token, ManualResetEvent evt)
{
for (int i = 0; i < 6; i++)
{
if (token.IsCancellationRequested)
{
return;
}
Sleep(1000);
}
evt.Set();
}
private static void CookieWait(CancellationTokenSource cts, bool isTimeOut)
{
if (isTimeOut)
{
cts.Cancel();
WriteLine("我下班了!!!");
}
else
{
WriteLine("开始做饭!!!");
}
}
}
}
我们注册了处理超时的异步操作。当接收到了 ManualRestEvent 对象的信号,工作者操作成功完成后会发出信号。如果操作完成之前超时,那么会使用 CancellationToken 来取消第一个操作。我们向线程池中放入一个耗时长的操作。它会运行 6 秒钟,如果成功完成则会设置一个 ManualResetEvent 信号类。在其他情况下,比如需要取消该操作,那么该操作会被丢弃。最后,为操作提供5秒的超时时间是不够的。这是因为操作会花费 6 秒来完成,只能取消该操作。所以如果提供 7 秒的超时时间是可行的,该操作会顺利完成。在有大量线程处于阻塞状态等待线程事件信号时这种方式非常有用。
二、计时器
我们前面所讲的都是一次性调用,那么如何进行周期性调用呢?这时我们就用到了计时器功能,下面我们通过例子来看一下。
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace _Timer_
{
class Program
{
static void Main(string[] args)
{
WriteLine("点击回车暂停计时器");
timer = new Timer(_ => TimerOpration(DateTime.Now), null, 1000, 2000);
try
{
Sleep(6000);
timer.Change(1000, 4000);
Read();
}
finally
{
timer.Dispose();
}
}
static Timer timer;
static void TimerOpration(DateTime dateTime)
{
TimeSpan elapsed = DateTime.Now - dateTime;
WriteLine($"{elapsed.Seconds} {dateTime} {CurrentThread.ManagedThreadId}");
}
}
}
我们首先创建 TimerOpration 方法传递一个起始时间,在方法中我们计算运行的时间差,并打印出来。同时我们打印出起始时间和进程 ID 。然后我们在主方法中初始化 Timer,第一个参数传入的时一个 lambda 表达式,它会在线程池中被执行。第二个参数时 null,是因为我们不需要知道用户状态对象。接着第三个参数指定了调用 TimerOpration 之前延迟的时间,也就是说延迟 N 秒后执行第一次。第四个参数代表间隔多久执行一次 TimerOpration 。最后我们 6 秒后我们修改计时器,在调用 Change 一秒后启动运行 TimerOpration 方法,以后每间隔 4 秒运行一次。
三、BackgroundWorker
在这一小节我们将不使用线程池和委托而是使用了事件。事件表示了一些通知的源或当通知到达时会有所响应的一系列订阅者。下面我们先来看一下例子。
using System;
using System.ComponentModel;
using static System.Console;
using static System.Threading.Thread;
namespace Background_Worker
{
class Program
{
static void Main(string[] args)
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += DoWork;
bw.ProgressChanged += ProgressChanged;
bw.RunWorkerCompleted += CompletedChanged;
bw.RunWorkerAsync();
WriteLine("输入E取消");
do
{
if(ReadKey(true).KeyChar=='E')
{
bw.CancelAsync();
}
} while (bw.IsBusy);
}
static void DoWork(object sender, DoWorkEventArgs e)
{
WriteLine($"DoWork 线程池线程ID: {CurrentThread.ManagedThreadId}");
BackgroundWorker bw = (BackgroundWorker)sender;
for (int i = 1; i <= 100; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
return;
}
if (i % 10 == 0)
{
bw.ReportProgress(i);
}
Sleep(100);
}
e.Result = 42;
}
static void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
WriteLine($"{e.ProgressPercentage} 已完成 。Progress 线程池线程ID: {CurrentThread.ManagedThreadId}");
}
static void CompletedChanged(object sender, RunWorkerCompletedEventArgs e)
{
WriteLine($"Completed 线程池线程ID: {CurrentThread.ManagedThreadId}");
if (e.Error != null)
{
WriteLine($"异常信息: {e.Error.Message} ");
}
else if (e.Cancelled)
{
WriteLine($"操作被取消");
}
else
{
WriteLine($"答案是: {e.Result}");
}
}
}
}
上述代码中我们创建了 BackgroundWorker 组件的实例。显式指出该后台工作者线程支持取消操作及该操作进度的通知。我们还定义了三个事件,当事件发生时会调用响应的事件处理器。每当事件通知订阅者时就会将具有特殊的定义签名的方法将被调用。我们可以只启动一个异步操作然后订阅给不同的事件。事件在操作执行时会被触发,这种方式被称为基于事件的异步模式。我们定义的 DoWork 事件会在后台工作对象通过 RunWorkerAsync 方法启动一个异步操作时被调用。我们在得到结果后将结果设置给事件参数,接着会运行 RunWorkerCompleted 事件处理器。在该方法中可以知道操作是成功完成、发生错误或被取消。BackgroundWorker 主要用于 WPF 中,通过后台工作事件处理器代码可以直接与 UI 控制器交互。与直接在线程池中与 UI 控制器交互的方式相比较,使用 BackgroundWorker 更好。
- SpringBoot之前端文件管理
- Spring Boot 设置静态资源访问
- IDEA更换主题
- 用正则表达式给字符串属性值都加上双引号
- Spring Boot修改启动端口
- Packet for query is too large (12238 > 1024). You can change this value
- win10下端口被占用解决办法
- 微信小程序开发教程第九章:微信小程序拍照收纳开发以及删除名片等
- centos 安装sbt
- 微信小程序开发教程第七章:微信小程序编辑名片页面开发
- idea中使用scala运行spark出现Exception in thread "main" java.lang.NoClassDefFoundError: scala/collection/Gen
- 使用JPA中@Query 注解实现update 操作
- 微信小程序开发教程!博卡君第二弹【微信小程序项目结构以及配置】
- WCF浅尝
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Bashtop:一款功能强大的LinuxOSXFreeBSD资源监控工具
- iOS 推送手机消息背后的技术
- 手把手教你使用 cert-manager 签发免费证书
- flink源码分析之TaskManager启动篇
- 【面试说】一年半前端 Bigo 一二三 面
- 【前端进阶】深入浅出浏览器事件循环【内附练习题】
- 【面试说】一年半前端 Shopee 面经
- 【前端进阶】深入浅出 JavaScript 中的 this
- 手把手带你玩转 AWS Lambda
- Guava 中的 Stopwatch 是个什么鬼?
- Mybatis接口Mapper内的方法为啥不能重载?
- 基于K-Means聚类算法的主颜色提取
- 扩展之Tensorflow2.0 | 21 Keras的API详解(下)池化、Normalization层
- 一个真实问题,搞定三个冷门pandas函数
- conda管理C源代码程序的时候总是出现库文件冲突或者缺失