前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >我没能实现始终在一个线程上运行 task

我没能实现始终在一个线程上运行 task

作者头像
newbe36524
发布于 2023-08-23 08:18:49
发布于 2023-08-23 08:18:49
25700
代码可运行
举报
运行总次数:0
代码可运行

前文我们总结了在使用常驻任务实现常驻线程时,应该注意的事项。但是我们最终没有提到如何在处理对于带有异步代码的办法。本篇将接受笔者对于该内容的总结。

如何识别当前代码跑在什么线程上

一切开始之前,我们先来使用一种简单的方式来识别当前代码运行在哪种线程上。

最简单的方式就是打印当前线程名称和线程ID来识别。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static void ShowCurrentThread(string work)
{
    Console.WriteLine($"{work} - {Thread.CurrentThread.Name} - {Thread.CurrentThread.ManagedThreadId}");
}

通过这段代码,我们可以非常容易的识别三种不同情况下的线程信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Test]
public void ShowThreadMessage()
{
    new Thread(() => { ShowCurrentThread("Custom thread work"); })
    {
        IsBackground = true,
        Name = "Custom thread"
    }.Start();

    Task.Run(() => { ShowCurrentThread("Task.Run work"); });
    Task.Factory.StartNew(() => { ShowCurrentThread("Task.Factory.StartNew work"); },
        TaskCreationOptions.LongRunning);

    Thread.Sleep(TimeSpan.FromSeconds(1));
}
// output
// Task.Factory.StartNew work - .NET Long Running Task - 17
// Custom thread work - Custom thread - 16
// Task.Run work - .NET ThreadPool Worker - 12

分别为:

  • 自定义线程 Custom thread
  • 线程池线程 .NET ThreadPool Worker
  • 由 Task.Factory.StartNew 创建的新线程 .NET Long Running Task

因此,结合我们之前昙花线程的例子,我们也可以非常简单的看出线程的切换情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Test]
public void ShortThread()
{
    new Thread(async () =>
    {
        ShowCurrentThread("before await");
        await Task.Delay(TimeSpan.FromSeconds(0.5));
        ShowCurrentThread("after await");
    })
    {
        IsBackground = true,
        Name = "Custom thread"
    }.Start();
    Thread.Sleep(TimeSpan.FromSeconds(1));
}
// output
// before await - Custom thread - 16
// after await - .NET ThreadPool Worker - 6

我们希望在同一个线程上运行 Task 代码

之前我们已经知道了,手动创建线程并控制线程的运行,可以确保自己的代码不会于线程池线程产生竞争,从而使得我们的常驻任务能够稳定的触发。

当时用于演示的错误示例是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Test]
public void ThreadWaitTask()
{
    new Thread(async () =>
    {
        ShowCurrentThread("before await");
        Task.Run(() =>
        {
            ShowCurrentThread("inner task");
        }).Wait();
        ShowCurrentThread("after await");
    })
    {
        IsBackground = true,
        Name = "Custom thread"
    }.Start();
    Thread.Sleep(TimeSpan.FromSeconds(1));
}
// output
// before await - Custom thread - 16
// inner task - .NET ThreadPool Worker - 13
// after await - Custom thread - 16

这个示例可以明显的看出,中间的部分代码是运行在线程池的。这种做法会在线程池资源紧张的时候,导致我们的常驻任务无法触发。

因此,我们需要一种方式来确保我们的代码在同一个线程上运行。

那么接下来我们分析一些想法和效果。

加配!加配!加配!

我们已经知道了,实际上,常驻任务不能稳定触发是因为 Task 会在线程池中运行。那么增加线程池的容量自然就是最直接解决高峰的做法。

因此,如果条件允许的话,直接增加 CPU 核心数实际上是最为有效和简单的方式。

不过这种做法并不适用于一些类库的编写者。比如,你在编写日志类库,那么其实无法欲知用户所处的环境。并且正如大家所见,市面上几乎没有日志类库中由说明让用户只能在一定的 CPU 核心数下使用。

因此,如果您的常驻任务是在类库中,那么我们需要一种更为通用的方式来解决这个问题。

考虑使用同步重载

在 Task 出现之后,很多时候我们都会考虑使用异步重载的方法。这显然不是错误的做法,因为这可以使得我们的代码更加高效,提升系统的吞吐量。但是,如果你想要让 Thread 稳定的在同一个线程上运行,那么你需要考虑使用同步重载的方法。通过同步重载方法,我们的代码将不会出现线程切换到线程池的情况。自然也就实现了我们的目的。

总是使用 TaskCreationOptions.LongRunning

这个办法其实很不实际。因为任何一层没有指定,都会将任务切换到线程池中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Test]
public void AlwaysLogRunning()
{
    new Thread(async () =>
    {
        ShowCurrentThread("before await");
        Task.Factory.StartNew(() =>
        {
            ShowCurrentThread("LongRunning task");
            Task.Run(() => { ShowCurrentThread("inner task"); }).Wait();
        }, TaskCreationOptions.LongRunning).Wait();
        ShowCurrentThread("after await");
    })
    {
        IsBackground = true,
        Name = "Custom thread"
    }.Start();
    Thread.Sleep(TimeSpan.FromSeconds(1));
}
// output
// before await - Custom thread - 16
// LongRunning task - .NET Long Running Task - 17
// inner task - .NET ThreadPool Worker - 7
// after await - Custom thread - 16

所以说,这个办法可以用。但其实很怪。

自定义 Scheduler

这是一种可行,但是非常困难的做法。虽然说自定义个简单的 Scheduler 也不是很难,只需要实现几个简单的方法。但要按照我们的需求来实现这个 Scheduler 并不简单。

比如我们尝试实现一个这样的 Scheduler:

注意:这个 Scheduler 并不能正常工作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyScheduler : TaskScheduler
{
    private readonly Thread _thread;
    private readonly ConcurrentQueue<Task> _tasks = new();

    public MyScheduler()
    {
        _thread = new Thread(() =>
        {
            while (true)
            {
                while (_tasks.TryDequeue(out var task))
                {
                    TryExecuteTask(task);
                }
            }
        })
        {
            IsBackground = true,
            Name = "MyScheduler"
        };
        _thread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return _tasks;
    }

    protected override void QueueTask(Task task)
    {
        _tasks.Enqueue(task);
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return false;
    }
}

上面的代码中,我们期待通过一个单一的线程来执行所有的任务。但实际上它反而是一个非常简单的死锁演示装置。

我们设想运行下面这段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Test]
public async Task TestLongRunningConfigureAwait()
{
    var scheduler = new MyScheduler();
    await Task.Factory.StartNew(() =>
    {
        ShowCurrentThread("BeforeWait");
        Task.Factory
            .StartNew(() =>
                {
                    ShowCurrentThread("AfterWait");
                }
                , CancellationToken.None, TaskCreationOptions.None, scheduler)
            .Wait();
        ShowCurrentThread("AfterWait");
    }, CancellationToken.None, TaskCreationOptions.None, scheduler);
}

这段代码中,我们期待,在一个 Task 中运行另外一个 Task。但实际上,这段代码会死锁。

因为,我们的 MyScheduler 中,我们在一个死循环中,不断的从队列中取出任务并执行。但是,我们的任务中,又会调用 Wait 方法。

我们不妨设想这个线程就是我们自己。

  1. 首先,老板交代给你一件任务,你把它放到队列中。
  2. 然后你开始执行这件任务,执行到一半发现,你需要等待第二件任务的执行结果。因此你在这里等着。
  3. 但是第二件任务这个时候也塞到了你的队列中。
  4. 这下好了,你手头的任务在等待你队列里面的任务完成。而你队列的任务只有你才能完成。
  5. 完美卡死。

因此,其实实际上我们需要在 Wait 的时候通知当前线程,此时线程被 Block 了,然后转而从队列中取出任务执行。在 Task 于 ThreadPool 的配合中,是存在这样的机制的。但是,我们自己实现的 MyScheduler 并不能与 Task 产生这种配合。因此需要考虑自定义一个 Task。跟进一步说,我们需要自定义 AsyncMethodBuilder 来实现全套的自定义。

显然者是一项相对高级内容,期待了解的读者,可以通过 UniTask^7 项目来了解如何实现这样的全套自定义。

总结

如果你期望在常驻线程能够稳定的运行你的任务。那么:

  1. 加配,以避免线程池不够用
  2. 考虑在这部分代码中使用同步代码
  3. 可以学习自定义 Task 系统

参考

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
前端架构师之10_JavaScript_DOM
第1级DOM(DOM Level 1,或DOM1)。为XML和HTML文档中的元素、节点、属性等提供了必备的属性和方法。结合了Netscape及微软公司开发的DHTML(动态HTML)思想。
张哥编程
2024/12/13
1790
JS之文档对象模型DOM
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gbk"> <title>History和Location使用</title> </head> <body> <input type="button" value="返回" onclick="history.back();" /> </body> </html> DOM 解析模型,将文档加载到 内存,形成一个树形结构 <html> 就是根节点,每个标签会成为
Java帮帮
2018/03/19
3.4K0
JS之文档对象模型DOM
JavaScript DOM基础
DOM(Document Object Model)即文档对象模型,针对HTML和XML文档的API(应用程序接口)。 一.DOM介绍 DOM中的三个字母,D(文档)可以理解为整个Web加载的网页文档;O(对象)可以理解为类似window对象之类的东西,可以调用属性和方法,这里我们说的是document对象;M(模型)可以理解为网页文档的树型结构。 DOM有三个等级,分别是DOM1、DOM2、DOM3,并且DOM1在1998年10月成为W3C标准。DOM1所支持的浏览器包括IE6+、Firefox、Safa
汤高
2018/01/11
1.4K0
HTML DOM(二):节点的增删改查
       上一篇:HTML DOM(一)        上一篇讲述了DOM的基本知识,从其得知,在DOM眼中,HTML的每个成分都可以看作是节点(文档节点、元素节点、文本节点、属性节点、注释节点,
高爽
2017/12/28
1.7K0
关于DOM的理解
当创建了一个网页并把它加载到web浏览器中时,DOM就悄然而生。浏览器根据网页文档创建一个文档对象。
Tz一号
2020/09/10
1K0
前端之BOM和DOM
BOM(Browser Object Model)浏览器对象模型,它使得JS能够与浏览器进行‘对话’(交互,通过JS对页面内容进行操作)。
GH
2019/12/16
2.8K0
前端之BOM和DOM
前端day13-JS(WebApi)学习笔记(attribute语法、DOM节点操作)
小技巧:如果API写的是Emement复数的形式,也就是后面加了s(Emements)那么它返回的就是一个伪数组 否则就是单个对象,一般只有id才会是单个对象,其他方式获取(标签名 类名)都是伪数组.
帅的一麻皮
2020/04/19
3.1K0
前端day13-JS(WebApi)学习笔记(attribute语法、DOM节点操作)
JavaScript 编程精解 中文第三版 十四、文档对象模型
当你在浏览器中打开网页时,浏览器会接收网页的 HTML 文本并进行解析,其解析方式与第 11 章中介绍的解析器非常相似。浏览器构建文档结构的模型,并使用该模型在屏幕上绘制页面。
ApacheCN_飞龙
2022/12/01
1.5K0
JavaScript 编程精解 中文第三版 十四、文档对象模型
从零开始学习BOM&amp;DOM
ECMAScript,描述了该语言的语法和基本对象,如类型、运算、流程控制、面向对象、异常等。
虎妞先生
2022/09/19
6140
从零开始学习BOM&amp;DOM
前端之HTML DOM操作
当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。
山河木马
2019/03/05
6090
JavaWeb——JavaScript精讲之DOM、BOM对象与案例实战(动态添加删除表格)
上一博文种讲解了JavaScript基础的ECMAScript,包括基本语法和部分对象,本文中继续讲解JavaScript中比较重要的两部分内容BOM、DOM及事件,后文中有对应的实战练习。
Winter_world
2020/09/25
2.3K0
JavaWeb——JavaScript精讲之DOM、BOM对象与案例实战(动态添加删除表格)
jQuery文档对象模型DOM的实际应用
DOM 在 JavaScript 课程中我们详细的探讨过,它是一种文档对象模型。方便开发者对 HTML 结构元素内容进行展示和修改。在 JavaScript 中,DOM 不但内容庞大繁杂,而且我们开发的过程中需要考虑更多的兼容性、扩展性。
王小婷
2018/12/19
1.2K0
DOM 文档对象模型
HTML 模板<html> <head> <title>我是网站标题</title> </head> <body> <div class="box"> <div class="box1"></div> </div> <div name="xiaoming"></div> <div id="box"></div> </body></html>访问节点通过 id 访问指定节点 getElement
菜园前端
2023/05/10
5160
javascript之DOM操作
http://www.cnblogs.com/kissdodog/archive/2012/12/25/2833213.html
bear_fish
2018/09/19
5560
js 深度解析DOM
因为document是window的一个属性,因为属性都是对象拥有的,所以他是一个object;
贵哥的编程之路
2020/11/03
5.1K0
js 深度解析DOM
E006Web学习笔记-JavaScript(四):DOM
将标记语言文档的各个部分,封装为对象,可以使用这些对象,对标记语言文档进行CRUD(增删改查)的动态操作;
訾博ZiBo
2025/01/06
890
E006Web学习笔记-JavaScript(四):DOM
3-DOM
将标记语言文档(HTML,XML…)的各个部分,封装为对象,可以使用这些对象,对标记语言文档进行CRUD动态操作
Ywrby
2022/10/27
1.4K0
第85节:Java中的JavaScript
后代选择器: 选择器1 选择器2 子元素选择器:选择器1 > 选择器2 选择器分组: 选择器1,选择器2,选择器3{} 属性选择器:选择器[属性名称='属性值']
达达前端
2019/07/03
2.7K0
第85节:Java中的JavaScript
【Java 进阶篇】深入理解 JavaScript DOM Node 对象
在前端开发中,与HTML文档进行交互是一项基本任务。文档对象模型(Document Object Model,简称DOM)为开发者提供了一种以编程方式访问和操作HTML文档的方式。DOM的核心是节点(Node)对象,它代表了文档中的各个部分。本博客将深入探讨JavaScript DOM Node对象,帮助您更好地理解它的作用和如何使用。
繁依Fanyi
2023/10/19
3700
Js DOM
要创建新的 HTML 元素 (节点)需要先创建一个元素,然后在已存在的元素中添加它。
hss
2022/02/25
3.9K0
相关推荐
前端架构师之10_JavaScript_DOM
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验