Какой вариант лучше в больших масштабах - циклическая задача для пользователя или отдельная задача, которая выполняет циклы и выполняет работу для всех пользователей? - PullRequest
1 голос
/ 23 апреля 2020

У меня есть все oop на пользователя, который выполняет задачу легкого веса, созданную с помощью Task.Factory.StartNew (). Задание в l oop выполняет легкую работу и спит несколько секунд.

Работает ли этот вид кода, если тысячи или миллионы пользователей работают одновременно? Будет ли управление этими многочисленными потоками создавать значительную нагрузку на сервер? Лучше ли иметь один поток, выполняющий эту работу для всех пользователей?

Это то, что происходит в коде в настоящее время,

Task.Factory.StartNew(() =>
{
  // begin loop
  // synchronous web call
  // process and update result in DB
  // exit condition
  // sleep for few minutes
  // end loop
}

1 Ответ

1 голос
/ 24 апреля 2020

У вас могут быть миллионы долго выполняющихся задач, но у вас не может быть миллионов долго выполняющихся потоков (если у вас нет машины с терабайтами ОЗУ, поскольку каждый поток выделяет 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);
...