У вас могут быть миллионы долго выполняющихся задач, но у вас не может быть миллионов долго выполняющихся потоков (если у вас нет машины с терабайтами ОЗУ, поскольку каждый поток выделяет 1 МБ ). Чтобы иметь так много задач, нужно сделать их асин c. Вместо того, чтобы спать с Thread.Sleep
, вы можете иметь их в ожидании асинхронно 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. В моем P C издержки составляют около 50 секунд при 100% потреблении процессора. Если вам нравится идея использования CancellationToken
s, обходной путь должен использовать альтернативу 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);