长时间运行的任务与线程——性能

Posted

技术标签:

【中文标题】长时间运行的任务与线程——性能【英文标题】:Long-running task vs. threads -- performance 【发布时间】:2017-10-22 19:40:19 【问题描述】:

假设我有一些长期运行的后台作业。每个作业都会做一些工作,然后抓取下一个作业并运行它,一直持续到时间结束。

目前这是使用任务实现的。我有一个JobStream,它在一个循环中一次运行一项工作。我可以同时运行 5、15 或 50 个这些流,具体取决于负载。

工作经理

public Task Run(CancellationToken cancellationToken) 
    var jobTasks = Enumerable
        .Range(0, _config.BackgroundProcessor.MaximumSimultaneousJobs)
        .Select(o => JobStream.StartNew(..., () => RunNextJob(cancellationToken), cancellationToken));

    return Task.WhenAll(jobTasks);

工作流

public static Task StartNew(Func<Task> nextJobRunner, CancellationToken cancellationToken) 
    var jobStream = new JobStream(nextJobRunner, cancellationToken);

    return jobStream.Start();


private Task Start() 
    return Task.Run(async () => 
        do 
            await _nextJobRunner();
         while (!_cancellationToken.IsCancellationRequested);
    );

我的问题是,任务在这里是一个很好的举措,还是我应该以老式的方式创建线程?我最关心的是性能和确保作业可以独立运行而不会因为另一个人正在努力工作而陷入困境。

【问题讨论】:

您确实应该为此使用 Microsoft 的反应式框架 (NuGet "System.Reactive")。它更强大、更简单。 我去看看,谢谢。 @JoshM.,它让我想起了this question。 你也可以试试TPL Dataflow 【参考方案1】:

您确实应该为此使用 Microsoft 的响应式框架 (NuGet "System.Reactive")。它更强大、更简单。

这是一个例子:

void Main()

    int number_of_streams = 10;

    IObservable<int> query =
        Observable
            .Range(0, number_of_streams)
            .Select(stream_number =>
                Observable
                    .Defer(() => Observable.Start(() => nextJob(stream_number)))
                    .Repeat())
            .Merge();

    IDisposable subscription =
        query
            .Subscribe(x => Console.WriteLine(x));


public int nextJob(int streamNumber)

    Thread.Sleep(10000);
    return streamNumber;

这会同时运行 10 个流并在每个流中调用 int nextJob(int streamNumber)。我为每个作业模拟了 10 秒的工作,但输出每秒都会产生一个结果。

此查询在 10 个流上永远重复,直到您调用 subscription.Dispose() 并且它会全部停止。

【讨论】:

欣赏答案。在这种情况下,我仍然对任务与线程感到好奇。 我认为你可以使用SelectMany 而不是Select -&gt; Merge @bradgonesurfing - 没错,但我认为这样更清楚。 也许你是对的。我倾向于使用SelectMany,但它确实有一种神奇的光环,使代码有点难以理解:)【参考方案2】:

@Enigmativity 提供的答案很好。

但是关于作业和线程之间的性能差异:

如果作业长时间运行且 CPU 密集型,则性能差异可以忽略不计。

如果作业运行时间长但不是 CPU 密集型,请使用任务,因为它既方便又节省创建线程的成本。

如果作业很短,请使用您自己的队列和老式多线程,因为 TPL 开销对于短期运行的作业来说非常重要。

与老式的多线程相比,task 是一种运行后台作业的便捷方式。它确实节省了创建线程的成本,但这种成本仅在您需要创建大量(数千个)线程时才重要。任务确实为排队和调度以及跟踪结果和异常增加了一些开销,但这仅在您创建大量(数十万个)时才重要。如果这些工作真的长时间运行,情况就不会如此。如果您需要处理这么多长时间运行的作业,那么您需要担心的问题与比较任务和线程之间的性能差异不同。

【讨论】:

以上是关于长时间运行的任务与线程——性能的主要内容,如果未能解决你的问题,请参考以下文章

通过将长时间运行的任务拆分为单独的进程来提高程序性能

具有长时间运行任务的单独线程中的计时器

长时间运行的任务,我应该在另一个线程或另一个进程中运行它们吗? [复制]

在后台运行长时间运行的并行任务,同时允许小型异步任务更新前台

在后台IOS实现长时间运行的任务

播放框架运行长时间阻塞任务,而不阻塞客户端