哪个选项在大规模情况下更好 - 每个用户的循环任务或循环并为所有用户完成工作的单个任务?
Which option is better on large scale - a looping task per user or a single task that loops and does the job for all users?
我为每个用户创建了一个循环,该循环执行使用 Task.Factory.StartNew() 创建的轻量级任务。循环中的任务执行轻量级工作并休眠几秒钟。
如果成千上万的用户同时工作,这种代码是否有效?
管理这么多线程是否会给服务器带来很大的负载?
让一个线程为所有用户完成这项工作是否更好?
这是当前代码中发生的情况,
Task.Factory.StartNew(() =>
{
// begin loop
// synchronous web call
// process and update result in DB
// exit condition
// sleep for few minutes
// end loop
}
您可以拥有数百万个长运行 任务,但不能拥有数百万个长运行 线程(除非您拥有一台拥有数 TB RAM 的机器,因为每个线程分配). The way to have so many tasks is to make them async. Instead of having them sleeping with Thread.Sleep
, you can have them awaiting asynchronously a Task.Delay
. 这是一个例子:
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = Enumerable.Range(1, 1_000_000).Select(index => Task.Run(async () =>
{
await Task.Delay(index, ct); // Initial delay to spread things out
while (true)
{
var webResult = await WebCallAsync(index, ct); // asynchronous web call
await DbUpdateAsync(webResult, ct); // update result in DB
await Task.Delay(1000 * 60 * 10, ct); // do nothing for 10 minutes
}
})).ToArray();
Task.WaitAll(tasks);
CancellationTokenSource
的目的是通过调用cts.Cancel()
随时取消所有任务。但是,将 Task.Delay
与取消结合使用会产生意想不到的开销,因为取消是通过 OperationCanceledException
异常传播的,而一百万个异常会对 .NET 基础结构造成相当大的压力。在我的 PC 中,开销约为 50 秒的 100% CPU 消耗。如果您确实喜欢使用 CancellationToken
的想法,解决方法是使用不会引发异常的替代 Task.Delay
。这是这个想法的实现:
/// <summary>Returns a <see cref="Task"/> that will complete with a result of true
/// if the specified number of milliseconds elapsed successfully, or false
/// if the cancellation token is canceled.</summary>
private static async Task<bool> NonThrowingDelay(int millisecondsDelay,
CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested) return false;
if (millisecondsDelay == 0) return true;
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => tcs.TrySetResult(false)))
using (new Timer(_ => tcs.TrySetResult(true), null, millisecondsDelay, Timeout.Infinite))
return await tcs.Task.ConfigureAwait(false);
}
下面是如何使用 NonThrowingDelay
方法创建 1,000,000 个可以(几乎)立即取消的任务:
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = Enumerable.Range(1, 1_000_000).Select(index => Task.Run(async () =>
{
if (!await NonThrowingDelay(index, ct)) return; // Initial delay
while (true)
{
var webResult = await WebCallAsync(index, ct); // asynchronous web call
await DbUpdateAsync(webResult, ct); // update result in DB
if (!await NonThrowingDelay(1000 * 60 * 10, ct)) break; // 10 minutes
}
})).ToArray();
Task.WaitAll(tasks);
我为每个用户创建了一个循环,该循环执行使用 Task.Factory.StartNew() 创建的轻量级任务。循环中的任务执行轻量级工作并休眠几秒钟。
如果成千上万的用户同时工作,这种代码是否有效? 管理这么多线程是否会给服务器带来很大的负载? 让一个线程为所有用户完成这项工作是否更好?
这是当前代码中发生的情况,
Task.Factory.StartNew(() =>
{
// begin loop
// synchronous web call
// process and update result in DB
// exit condition
// sleep for few minutes
// end loop
}
您可以拥有数百万个长运行 任务,但不能拥有数百万个长运行 线程(除非您拥有一台拥有数 TB RAM 的机器,因为每个线程分配Thread.Sleep
, you can have them awaiting asynchronously a Task.Delay
. 这是一个例子:
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = Enumerable.Range(1, 1_000_000).Select(index => Task.Run(async () =>
{
await Task.Delay(index, ct); // Initial delay to spread things out
while (true)
{
var webResult = await WebCallAsync(index, ct); // asynchronous web call
await DbUpdateAsync(webResult, ct); // update result in DB
await Task.Delay(1000 * 60 * 10, ct); // do nothing for 10 minutes
}
})).ToArray();
Task.WaitAll(tasks);
CancellationTokenSource
的目的是通过调用cts.Cancel()
随时取消所有任务。但是,将 Task.Delay
与取消结合使用会产生意想不到的开销,因为取消是通过 OperationCanceledException
异常传播的,而一百万个异常会对 .NET 基础结构造成相当大的压力。在我的 PC 中,开销约为 50 秒的 100% CPU 消耗。如果您确实喜欢使用 CancellationToken
的想法,解决方法是使用不会引发异常的替代 Task.Delay
。这是这个想法的实现:
/// <summary>Returns a <see cref="Task"/> that will complete with a result of true
/// if the specified number of milliseconds elapsed successfully, or false
/// if the cancellation token is canceled.</summary>
private static async Task<bool> NonThrowingDelay(int millisecondsDelay,
CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested) return false;
if (millisecondsDelay == 0) return true;
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => tcs.TrySetResult(false)))
using (new Timer(_ => tcs.TrySetResult(true), null, millisecondsDelay, Timeout.Infinite))
return await tcs.Task.ConfigureAwait(false);
}
下面是如何使用 NonThrowingDelay
方法创建 1,000,000 个可以(几乎)立即取消的任务:
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = Enumerable.Range(1, 1_000_000).Select(index => Task.Run(async () =>
{
if (!await NonThrowingDelay(index, ct)) return; // Initial delay
while (true)
{
var webResult = await WebCallAsync(index, ct); // asynchronous web call
await DbUpdateAsync(webResult, ct); // update result in DB
if (!await NonThrowingDelay(1000 * 60 * 10, ct)) break; // 10 minutes
}
})).ToArray();
Task.WaitAll(tasks);