前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >6搞懂线程池(二)

6搞懂线程池(二)

作者头像
喵叔
发布于 2020-09-08 08:50:40
发布于 2020-09-08 08:50:40
51200
代码可运行
举报
文章被收录于专栏:喵叔's 专栏喵叔's 专栏
运行总次数:0
代码可运行

抱歉各位多线程专栏托更这么久,这篇文章我们继续讲线程池的相关知识,其中将涉及到如下知识:

  1. 取消异步操作
  2. 等待事件处理器及超时
  3. 计时器
  4. BackgroundWorker
零、取消异步操作

这一小节将引入两个类 CancellationTokenSource 和 CancellationToken 。这两个类是在 .NET 4.0 中被引入的,因此如果需要使用这两个类我们必须在 .NET 4.0 及其以上版本中使用,目前是取消异步操作的标准。下面我们通过厨师做饭,中途撤销订单的例子来看一下这两个类具体该怎么用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 方法修改一下,用另一种方式来实现取消操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void Cookie(CancellationToken token)
{
    try
    {
        WriteLine("开始做饭.......");
        for (int i = 0; i < 5; i++)
        {
            token.ThrowIfCancellationRequested();
            Sleep(2000);
        }
        WriteLine("我做完饭了");
    }
    catch(OperationCanceledException)
    {
        WriteLine("取消做饭");
    }
}

这种方法我们抛出一个 OperationCancelledException 异常。这允许我们在线程池之外控制取消执行过程。需要取消操作时通过操作之外的代码来处理。下面我们再来修改一下 Cookie 方法,用第三种方法来是先取消操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 。它允许我们把回调函数放入线程池,每当等待事件处理器收到信号或者等待超时时将执行这个回调函数。下面的代码通过模拟初始等待下单做饭,到了下班时间(超时)后就停止接单。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 秒的超时时间是可行的,该操作会顺利完成。在有大量线程处于阻塞状态等待线程事件信号时这种方式非常有用。

二、计时器

我们前面所讲的都是一次性调用,那么如何进行周期性调用呢?这时我们就用到了计时器功能,下面我们通过例子来看一下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

在这一小节我们将不使用线程池和委托而是使用了事件。事件表示了一些通知的源或当通知到达时会有所响应的一系列订阅者。下面我们先来看一下例子。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 更好。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/01/13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
蓝桥杯 试题 基础练习 矩阵乘法
思路:需要了解矩阵的相关性质,矩阵的0次幂为单位矩阵,及主对角线为1,其余的都为0,矩阵的1次幂为本身,当大于等于2时,需要每次等到一次相乘后的矩阵后,赋值给另一个数组,然后幂次数减一,如此直到循环结束
杨鹏伟
2020/09/10
3840
BZOJ4128: Matrix(BSGS 矩阵乘法)
第一行两个整数n和p,表示矩阵的阶和模数,接下来一个n * n的矩阵A.接下来一个n * n的矩阵B
attack
2018/07/27
3610
备战第十六届蓝桥杯——函数——实践练习答案
改变数据结构的视角:矩阵转置实际上是对矩阵的一种重新组织。原始矩阵的行变成了转置矩阵的列,原始矩阵的列变成了转置矩阵的行。这种转换提供了一种从不同维度观察数据的方式,有助于发现数据在不同方向上的规律和关系。
红目香薰
2024/11/04
1180
备战第十六届蓝桥杯——函数——实践练习答案
算法系列-----矩阵(四)-------------矩阵的乘法
而如果该函数被下面调用了,已经判断了a的长度和b的长度是相等的,所以这里只是单独的抽出来而已
wust小吴
2022/03/04
5180
SDAccel矩阵乘法优化(三)
承接第二篇Local Memory的实现方法,接下来进一步进行矩阵乘法的优化处理。本文主要解决gmem carry dependency的问题。在这里,不采用Max Memory Ports的方法,因为采用多个接口会消耗大量的LUT资源,并且大大的限制时钟频率的提升。其实,前面分析过了造成gmem carry dependency的原因,在矩阵乘法的实现过程中,我们完全可以将两个输入的数据分离,不需要在一个for循环中同时进行数据的读取而导致一个for循环在pipeline的过程中需要对两个接口进行读取的问题。因此我们改进代码,将两个输入数据分离并实现Burst突发传输。
AI异构
2020/07/29
6400
SDAccel矩阵乘法优化(三)
基础练习 矩阵乘法
  给定一个N阶矩阵A,输出A的M次幂(M是非负整数)   例如:   A =   1 2   3 4   A的2次幂   7 10   15 22
刘开心_1266679
2019/02/14
8880
C:9-9题目:蛇形矩阵
给你两个整数n,m,请你构造一个n行m列的蛇形方阵,在这个方阵中,数字由1到n×m,从最右上角开始,呈环状(逆时针)向内填充。
LonlyMay
2024/10/21
1920
C:9-9题目:蛇形矩阵
【重拾C语言】六、批量数据组织(一)数组(数组类型、声明与操作、多维数组;典例:杨辉三角、矩阵乘积、消去法)
C语言中的数组是一种用于存储多个相同类型元素的数据结构。它是一种线性数据结构,可以按照索引访问和操作其中的元素。数组在C语言中被广泛应用于各种编程任务,包括数据的组织、存储和处理。同时,数组也是其他数据结构和算法的基础,如字符串、栈、队列、排序算法等。
Qomolangma
2024/07/30
930
【重拾C语言】六、批量数据组织(一)数组(数组类型、声明与操作、多维数组;典例:杨辉三角、矩阵乘积、消去法)
矩阵求逆的几种方法总结(C++)
文内程序旨在实现求逆运算核心思想,某些异常检测的功能就未实现(如矩阵维数检测、矩阵奇异等)。
xiaoxi666
2018/10/29
10.8K0
每日一题(1)
矩阵相乘最重要的方法是一般矩阵乘积。它只有在第一个矩阵的列(column)和第二个矩阵的行数(row)相同时才有意义 。一般单指矩阵乘积时,指的便是一般矩阵乘积。一个m×n的矩阵就是m×n个数排成m行n列的一个数阵。由于它把许多数据紧凑的集中到了一起,所以有时候可以简便地表示一些复杂的模型。
C语言与CPP编程
2020/12/02
4720
每日一题(1)
Strassen矩阵乘法问题(Java)
矩阵乘法是线性代数中最常见的问题之一 ,它在数值计算中有广泛的应用。 设A和B是2个nXn矩阵, 它们的乘积AB同样是一个nXn矩阵。 A和B的乘积矩阵C中元素C[i][j]定义为:
WHYBIGDATA
2023/01/31
7170
Strassen矩阵乘法问题(Java)
GPU编程(三): CPU与GPU的矩阵乘法对比
前言 在上一篇的最后, 我提到了一个矩阵乘法, 这次与CPU进行对比, 从中可以很明显GPU在并行计算上的优势. ---- 计时函数 在贴出代码之前, 来看下我常用的计时函数, 可以精确到微秒级. 首先头文件是#include<sys/time.h>. 结构体为: struct timeval{ long tv_sec; /*秒*/ long tv_usec; /*微秒*/ }; 来看下使用的小栗子: struct timeval start, end; double t
sean_yang
2019/01/28
1.7K0
GPU编程(三): CPU与GPU的矩阵乘法对比
用c++实现矩阵的运算以及用矩阵的方式输出矩阵
matrix的构造函数 动态开辟空间,实现添加矩阵。  析构函数 释放动态开辟的空间,防止内存泄露。  重载“+ - * /”运算符  为了方便输出 顺便实现 << 运算符
用户7886150
2021/02/05
2.1K0
【数据结构】数组和字符串(一):数组的基本操作、矩阵的数组表示
  数组是一种数据结构,用于存储相同类型的元素序列。它是在内存中连续存储的一组相同类型的数据。数组在计算机科学和编程中扮演着重要的角色,因为它们能够有效地存储和访问大量数据。
Qomolangma
2024/07/30
1560
【数据结构】数组和字符串(一):数组的基本操作、矩阵的数组表示
pHash的Java实现
此算法中的DCT变换是从 http://blog.csdn.net/luoweifu/article/details/8214959抄过来的,因为这种需要大量的复杂的数学运算,我看不来,完全不懂...都已经还给老师了... package com.imageretrieval.features; import com.imageretrieval.utils.ImageUtil; /** * pHash<br> * 参考链接:http://blog.csdn.net/zouxy09/article/
Venyo
2018/03/15
2.5K0
矩阵求逆c++实现[通俗易懂]
高斯消元法可以用来找出一个可逆矩阵的逆矩阵。设A 为一个N * N的矩阵,其逆矩阵可被两个分块矩阵表示出来。将一个N * N单位矩阵 放在A 的右手边,形成一个N * 2N的分块矩阵B = [A,I] 。经过高斯消元法的计算程序后,矩阵B 的左手边会变成一个单位矩阵I ,而逆矩阵A ^(-1) 会出现在B 的右手边。假如高斯消元法不能将A 化为三角形的格式,那就代表A 是一个不可逆的矩阵。应用上,高斯消元法极少被用来求出逆矩阵。高斯消元法通常只为线性方程组求解。
全栈程序员站长
2022/09/25
1.7K0
矩阵求逆c++实现[通俗易懂]
SDAccel矩阵乘法优化(四)
现在经过前面两次优化后,代码的组织结构没有什么问题了,现在的关键问题是:矩阵运算的嵌套for循环仅仅实现了内层的pipeline,因为外层for循环无法对内部的for循环flatten,所以外面两层的for循环没有实现pipeline。要解决这个问题,最直接的思路就是将最内层的for循环直接进行循环展开,进一步提高计算过程的并行度。但是在进行循环展开的过程中,需要将内层用到的数组进行切割,否则将无法进行unroll。因此,我们将用到的指令有三个:内层for循环要进行循环展开(unroll),并行计算用到的数组要进行数组切割(array partition),次外层的for循环要流水起来(pipeline)。
AI异构
2020/07/29
1.3K0
SDAccel矩阵乘法优化(四)
线性代数整理(二)正交性,标准正交矩阵和投影坐标转换和线性变换
单从一个矩阵来看,我们可以发现该矩阵的列秩和行秩是相等的,都为2,那么这是一个特例还是矩阵的特性呢?其实这是矩阵的特性,矩阵的行秩=矩阵的列秩。
算法之名
2021/03/02
1.5K0
线性代数整理(二)正交性,标准正交矩阵和投影坐标转换和线性变换
SDAccel矩阵乘法优化(二)
首先,我们先进行访存上的优化。原始版本的矩阵乘法实现虽然简单,但是在进行计算的过程中需要频繁的与DDR进行数据交互,但是DDR与FPGA进行交互的过程中是十分耗费时间与功耗的,因此,我们需要在FPGA上开一个局部的存储空间,先将数据从DDR搬运到FPGA片上的存储空间上,然后再进行计算,计算的过程数据在片上的空间进行索引,最后将计算完的数据再统一搬运回DDR上。这样,在片上的计算过程就不会频繁的受到DDR与FPGA访存慢的限制。
AI异构
2020/07/29
5480
SDAccel矩阵乘法优化(二)
AI部署篇 | CUDA学习笔记2:矩阵乘法与GPU优化(附CUDA代码)
获得 C 矩阵的计算方法都是相同的,只不过使用的是矩阵 A、B 不同的元素来进行计算,即不同数据的大量相同计算操作,这种计算是特别适合使用GPU来计算,因为GPU拥有大量简单重复的计算单元,通过并行就能极大的提高计算效率。
集智书童公众号
2022/02/10
5.9K0
AI部署篇 | CUDA学习笔记2:矩阵乘法与GPU优化(附CUDA代码)
推荐阅读
相关推荐
蓝桥杯 试题 基础练习 矩阵乘法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档