асинхронные методы, использующие или не использующие пул потоков, масштабируемые или нет - PullRequest
0 голосов
/ 19 октября 2018

Не могли бы вы помочь мне понять разницу между двумя методами здесь:

1) почему первый использует пул потоков, а второй нет?

2) Почему второй дает вам масштабируемость, а первый нет - это связано с пулами потоков?

3) Как я могу вызвать два метода в main, чтобы сделать ихЦель более очевидна (а лучше освещать их различия)?

4) Я знаю, что асинхронные методы задач можно вызывать с помощью await, а как насчет задач, в которых отсутствует асинхронность, подобных этим методам?

public class _12_MyClass
{

    public Task SleepAsyncA(int millisecondsTimeout)
    {
        return Task.Run(() => Thread.Sleep(millisecondsTimeout));
    }

    public Task SleepAsyncB(int millisecondsTimeout)
    {
        TaskCompletionSource<bool> tcs = null;
        var t = new Timer(delegate { tcs.TrySetResult(true); }, null, -1, -1);

        tcs = new TaskCompletionSource<bool>(t);
        t.Change(millisecondsTimeout, -1);

        return tcs.Task;
    }

    public static void Main()
    {
    }
}

1 Ответ

0 голосов
/ 20 октября 2018

На этот вопрос трудно ответить, поскольку он сравнивает яблоки с апельсинами, утверждает, что они являются фруктами, и пытается сравнить их друг с другом.

1) Почему первый использует пул потоков ивторой нет? **

2) Почему второй дает вам масштабируемость, а первый нет - это связано с пулами потоков?

  • По умолчанию типы TPL , такие как Task и Task<TResult>, используют потоки пула потоков для запуска задач.SleepAsyncA запускает задачу, затем вызывает Thread.Sleep, которая ничего не делает, но блокирует текущий поток на определенный период времени.В результате все, что вы сделали, это сделали таймер, украдя поток пула потоков и блокируя его, пока вы не закончите.Очевидно, что если вы сохраняете этот масштабированный масштаб, у вас закончатся потоки пула потоков.

  • SleepAsyncB работает с таймером.System.Threading.Timer отправляет обратный вызов в потоке пула потоков вместо создания нового потока каждый раз.В этом случае он просто устанавливает результат на TaskCompletionSource на мгновение.

    • Тип TaskCompletionSource<TResult> служит двум связанным целям: это источник для создания задачи и источник для завершения этой задачи.A TaskCompletionSource<TResult> выступает в качестве производителя для Task<TResult> и его завершения.В отличие от задач, созданных Task.Run и т. П., Task, выданный TaskCompletionSource<TResult>, не имеет ни одного запланированного делегата, связанного с ним, однако предоставляет методы, позволяющие контролировать время жизни и завершение связанной задачи.

    • По сути, вы берете шаблон события, созданный Timer, и используете TaskCompletionSouce, чтобы заставить его действовать как Task.Тайм-аут не блокирует ни одного потока, поэтому он более масштабируемый.

3) Как вызвать два метода в main, чтобы сделать ихЦель более очевидна (и лучше подчеркнуть их различия)?

Учитывая

public static string DebugInfo
{
   get
   {
      ThreadPool.GetMaxThreads(out var maxThreads, out _);
      ThreadPool.GetAvailableThreads(out var threads, out _);
      var usedThreads = maxThreads - threads;
      var mt = $"{usedThreads.ToString().PadLeft(4)}/{maxThreads.ToString().PadLeft(4)}";
      return $"Threads {mt.PadRight(8)}";
   }
}
public static Task SleepAsyncA(int millisecondsTimeout)
{
   return Task.Run(() => { Console.WriteLine("SleepAsyncA " + DebugInfo);  Thread.Sleep(millisecondsTimeout); });
}

public static Task SleepAsyncB(int millisecondsTimeout)
{
   TaskCompletionSource<bool> tcs = null;
   var t = new Timer(delegate { tcs.TrySetResult(true); }, null, -1, -1);

   Console.WriteLine("SleepAsyncB " + DebugInfo);

   tcs = new TaskCompletionSource<bool>(t);
   t.Change(millisecondsTimeout, -1);

   return tcs.Task;
}

Использование

   var ms = 5000;
   Console.WriteLine("Start " + DebugInfo);
   var list = Enumerable.Range(0, 10).Select(x => SleepAsyncA(ms));

   Task.WaitAll(list.ToArray());

   var list2 = Enumerable.Range(0, 10).Select(x => SleepAsyncB(ms));
   Task.WaitAll(list2.ToArray());

Вывод

Start Threads    0/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    8/2047
SleepAsyncA Threads    9/2047
SleepAsyncA Threads   10/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047
SleepAsyncB Threads    0/2047

Полная демонстрация здесь

4) Я знаю асинхронные методы задачиможно вызывать с помощью await, а как насчет задач, в которых отсутствует асинхронность, подобных этим методам?

Конечно!и это общая схема, позволяющая избежать лишнего конечного автомата и обеспечивающая вам эффективность.Однако каждый раз, когда вы вызываете await, ваш метод должен быть помечен как async

private static async Task Main(string[] args)
{
   var ms = 5000;
   await SleepAsyncA(ms);
   await SleepAsyncB(ms);
}

Наконец, почему бы просто не сохранить все хлопоты и лишний код, а просто использовать Task.Delay(ms)

private static async Task Main(string[] args)
{
   await Task.Delay(ms); 
}

Масштабируемый, простой, без суеты.

...