解决一个问题

假如,程序需要向一个 Web 发送 5 次请求,受网路波动影响,有一定几率请求失败。如果失败了,就需要重试。

示例代码如下:

    class Program
{
private static int count = 0;
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
new Thread(HttpRequest).Start(); // 创建线程 // 用于不断向另一个线程发送信号
while (count < 5)
{
Thread.Sleep(100);
}
Console.WriteLine("任务执行完毕");
} // 模拟网络请求
public static void HttpRequest()
{
Console.WriteLine("开始一个任务");
// 随机生成一个数,如果为偶数,则模拟请求失败
bool isSuccess = (new Random().Next(0, 10)) % 2 == 0; // ... ...模拟请求 HTTP
Thread.Sleep(TimeSpan.FromSeconds(2)); // 请求失败则重试
if (!isSuccess)
{
Console.WriteLine($"请求失败,count={count}");
new Thread(() =>
{
HttpRequest();
}).Start();
return;
}
// 完成一次任务,+1
Interlocked.Add(ref count,1);
Console.WriteLine($"完成任务,count={count}");
}
}

代码太糟糕了,但我们可以使用 CountdownEvent 类来改造它。

CountdownEvent 类

表示在计数变为零时处于有信号状态的同步基元。

也就是说,设定一个计数器,每个线程完成后,就会减去 1 ,当计数器为 0 时,代表所有线程都已经完成了任务。

构造函数和方法

CountdownEvent 类的构造函数如下:

构造函数 说明
CountdownEvent(Int32) 使用指定计数初始化 CountdownEvent 类的新实例。

CountdownEvent 类的常用方法如下:

方法 说明
AddCount() 将 CountdownEvent 的当前计数加 1。
AddCount(Int32) 将 CountdownEvent 的当前计数增加指定值。
Reset() 将 CurrentCount 重置为 InitialCount 的值。
Reset(Int32) 将 InitialCount 属性重新设置为指定值。
Signal() 向 CountdownEvent 注册信号,同时减小 CurrentCount 的值。
Signal(Int32) 向 CountdownEvent 注册多个信号,同时将 CurrentCount 的值减少指定数量。
TryAddCount() 增加一个 CurrentCount 的尝试。
TryAddCount(Int32) 增加指定值的 CurrentCount 的尝试。
Wait() 阻止当前线程,直到设置了 CountdownEvent 为止。
Wait(CancellationToken) 阻止当前线程,直到设置了 CountdownEvent 为止,同时观察 CancellationToken。
Wait(Int32) 阻止当前线程,直到设置了 CountdownEvent 为止,同时使用 32 位带符号整数测量超时。
Wait(Int32, CancellationToken) 阻止当前线程,直到设置了 CountdownEvent 为止,并使用 32 位带符号整数测量超时,同时观察 CancellationToken。
Wait(TimeSpan) 阻止当前线程,直到设置了 CountdownEvent 为止,同时使用 TimeSpan 测量超时。
Wait(TimeSpan, CancellationToken) 阻止当前线程,直到设置了 CountdownEvent 为止,并使用 TimeSpan 测量超时,同时观察 CancellationToken。

API 比较多,没事,我们来慢慢了解它。

示例

我们来编写一个场景代码,一个有五件事,需要完成,分别派出 5 个人去实现。

.Wait(); 用在一个线程中,这个线程将等待其它完成都完成任务后,才能继续往下执行。

Signal(); 用于工作线程中,向 CountdownEvent 对象发送信号,告知线程已经完成任务,然后 CountdownEvent.CurrentCount 将减去 1。

当计数器为 0 时,阻塞的线程将恢复执行。

代码示例如下:

    class Program
{
// 手头上有 5 件事
private static CountdownEvent countd = new CountdownEvent(5);
static void Main(string[] args)
{
Console.WriteLine("开始交待任务");
// 同时叫 5 个人,去做 5 件事
for (int i = 0; i < 5; i++)
{
Thread thread = new Thread(DoOne);
thread.Name = $"{i}";
thread.Start();
} // 等他们都完成事情
countd.Wait(); Console.WriteLine("任务完成,线程退出");
Console.ReadKey();
} public static void DoOne()
{
int n = new Random().Next(0, 10);
// 模拟要 n 秒才能完成
Thread.Sleep(TimeSpan.FromSeconds(n));
// 完成了,减去一件事
countd.Signal();
Console.WriteLine($" {Thread.CurrentThread.Name}完成一件事了");
}
}

示例很简单,每个线程在完成自己的任务时,需要调用 Signal() 方法,使得计数器减去1。

.Wait(); 可以等待所有的任务完成。

需要注意的是,如果不调用 Signal() 或者计数器一直不为0,那么 Wait() 将无限等待。

当然,Wait() 可以设置等待时间,

另外我们也看到了常用方法中有 AddCount()Reset()等。

这个类的等待控制方式比较宽松,Wait() 后,到底什么时候才能执行,全凭其它线程自觉。

如果发现线程执行任务失败,我们可以不调用 Signal() 或者 使用 AddCount() 来增加次数,进行重试

C#多线程(8):线程完成数的更多相关文章

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

    在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...

  2. Java学习笔记-多线程-创建线程的方式

    创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...

  3. Java多线程开发系列之四:玩转多线程(线程的控制1)

    在前文中我们已经学习了:线程的基本情况.如何创建多线程.线程的生命周期.利用已有知识我们已经可以写出如何利用多线程处理大量任务这样简单的程序.但是当应用场景复杂时,我们还需要从管理控制入手,更好的操纵 ...

  4. iOS开发多线程篇—线程间的通信

    iOS开发多线程篇—线程间的通信 一.简单说明 线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 线程间通信的体现 1个线程传递数据给另1个线程 在1个线程中执行完特定任 ...

  5. 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法

    [源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...

  6. .net学习之多线程、线程死锁、线程通信 生产者消费者模式、委托的简单使用、GDI(图形设计接口)常用的方法

    1.多线程简单使用(1)进程是不执行代码的,执行代码的是线程,一个进程默认有一个线程(2)线程默认情况下都是前台线程,要所有的前台线程退出以后程序才会退出,进程里默认的线程我们叫做主线程或者叫做UI线 ...

  7. Java多线程之线程的同步

    Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...

  8. Java多线程之线程的控制

    Java多线程之线程的控制 线程中的7 种非常重要的状态:  初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...

  9. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  10. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

随机推荐

  1. 【0基础学爬虫】爬虫基础之自动化工具 Pyppeteer 的使用

    大数据时代,各行各业对数据采集的需求日益增多,网络爬虫的运用也更为广泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前已经推出不少爬虫进阶.逆向相关文章,为实现从易到难全方位覆盖,特设[0基础学爬 ...

  2. ABP vNext系列文章04---DynamicClient动态代理

    一.动态代理在ABP系统中的应用 1.它主要在做什么事件 之前开发系统想要在后台调用别的服务都是用HttpClient发起请求,在abp vnext中不需要我们这样做了, 你只要知道服务调用的接口方法 ...

  3. 分页sql大全

    一.排除Top分页法(自命名,非规范) 思想:所谓"排除Top分页",主要依靠"排除"和Top这个两大核心步骤.首先查询当前页码之前的数据,然后将该数据从总数据 ...

  4. 一文总结现代 C++ 中的初始化

    本文尝试回答: 现代 C++ 有哪几种初始化形式?分别能够用于什么场景?有什么限制? MyClass obj(); 为什么没有调用默认无参构造函数创建一个对象? new int 和 new int() ...

  5. 期盼已久全平台支持-开源IM项目OpenIM之uniapp更新

    国内uniapp使用广泛,OpenIM的uniapp sdk以及文档和demo (https://github.com/OpenIMSDK/Open-IM-Uniapp-Demo)都已更新,本文主要展 ...

  6. 3.3 Windows驱动开发:内核MDL读写进程内存

    MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式.在Windows操作系统中,每个进程都有自己独立的虚拟地址空间,不同进程之间的内存空间是隔离的.因此,要在一个进程中读取或写入另一个 ...

  7. CE修改器入门:寻找指针基址

    上一步阐述了如何使用代码替换功能对付变化位置的数据地址,但这种方法往往不能达到预期的效果,所以我们需要学习如何利用指针,在本关的 Tutorial.exe 窗口下面有两个按钮,一个会改变数值,另一个不 ...

  8. 使用DoraCloud构建远程办公桌面云

    公司总部在上海.员工分布在各地.部分员工需要远程办公.为了实现远程办公,有几种备选方案. 方案1.在员工的PC上安装向日葵.ToDesk之类的远程工具. 方案2.公司总部提供VPN,员工通过VPN拨号 ...

  9. Promise, async, await实现异步编程,代码详解

    写在开头 一点题外话 其实最近在不断的更新Java的知识,从基础到进阶,以及计算机基础.网络.WEB.数据库.数据结构.Linux.分布式等等内容,预期写成一个既可以学习提升又可以面试找工作的< ...

  10. 小知识:RMAN备份当前控制文件报错ORA-245

    在一个备份的case上遇到备份控制文件报错ORA-245,最终通过修改snapshot controlfile默认位置到ASM磁盘组后解决. 1.问题复现 回来后就想快速记录下这个小知识点,打开尘封的 ...