一个BackgroundService始终会在一个新线程中运行吗?

10
在ASP.Net Core的MSDN中,它展示了如何使用托管服务创建后台任务。甚至有一个特定段落解释了如何创建后台队列。
现在我的问题是,ExecuteAsync方法是否会在自己的线程中运行,还是我需要先调用Task.Run
3个回答

6

BackgroundService 是否始终在新线程中运行? 不是。

BackgroundService 并未指定任何关于线程的内容。它唯一要求的是提供一个Task类型的重载,只要服务保持运行状态,该任务将一直保持活动状态。你甚至可以返回一个已完成的任务。

如果你查看源代码,你会发现根本没有任何假设:

    protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        return Task.CompletedTask;
    }

服务方法的线程行为取决于实现者,也就是你。如果ExecuteAsync在产生yield之前被阻塞,则整个服务都会被阻塞。如果该方法从未产生yield,则调用StartAsync本身将被阻塞,并对整个应用程序造成问题。
如果ExecuteAsync在第一个await之前执行了一些昂贵的操作,则启动其他服务也将被延迟。
这意味着如果服务在第一次yield之前需要执行任何昂贵的操作,即第一次调用await,则可能需要使用Task.Run

4
假设ExecuteAsync是一个async方法(public async Task ExecuteAsync),那么它是否在自己的线程中运行取决于具体情况。简而言之,async意味着这个线程可以被await。当执行await时,主线程的当前执行会被挂起,直到async返回结果。这将释放当前线程回到线程池以供重新使用。然后,当async返回时,将从线程池中拉出一个新线程(也许取决于实际调用方式)继续执行。这称为上下文切换。如果这个方法不是真正的async,那么什么也不会发生,它会像非async方法一样运行。
如果该方法明确创建了一个Task(使用Task.Run),那么async线程将awaitTask。因此,任务使用新线程,并且在任务返回时async方法将释放其线程并获得一个新线程。这不是零和游戏,因为上下文切换是昂贵的。这就是为什么您应该只asyncIO绑定方法,因为在CPU绑定过程中通常会失去效率而不是获得效率。
我建议您阅读Stephen Cleary关于此主题的优秀博客

1
主要问题没有答案:HostedService是否总是在单独的线程中运行? - Denis Babarykin
主要问题的答案是:“这取决于情况”,正如我在我的答案@DenisBabarykin中所述。关于它是否总是运行,如下面所述,不是的 - Liam

1

异步编程开始

对于I/O绑定的代码,您需要在async方法中等待返回Task或Task的操作。

对于CPU绑定的代码,您需要使用Task.Run方法在后台线程上启动操作。

因此,如果您的ExecuteAsync方法是I/O绑定的(根据名称看起来是I/O绑定),则不需要调用Task.Run

但是,当该方法是CPU绑定的时(即您的代码正在执行计算),则应调用Task.Run在后台运行。


那我不应该在Enqueued项内部执行Task.Run吗? - Twenty
答案是不。 - Mukul Keshari
当被排队的任务不是CPU绑定时,为什么不这样做呢?调用Task.Run将是无用的。 - Twenty
如果您对任何异步方法使用Task.Run,那么它将是无用的。 - Mukul Keshari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接