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



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

这目前是使用任务实现的。我有一个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的Reactive Framework(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()并且全部停止。

@Enigmativity提供的答案很好。

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

如果作业长时间运行且占用大量 CPU,则性能差异可以忽略不计。

如果作业长时间运行但不占用大量 CPU 资源,请使用任务,因为它很方便并且可以节省创建线程的成本。

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

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

最新更新