Что такое Task.RunSynchronously?

Мне просто интересно, для чего нужен метод? В каком сценарии я могу использовать этот метод.

Моя первоначальная мысль - RunSynchronously - это вызов метода async и запуск этого синхронно, не вызывая проблемы с тупиковой ситуацией, как то, что .wait().

Однако, согласно MSDN,

Обычно задачи выполняются асинхронно в потоке пула потоков и не блокируют вызывающий поток. Задачи, выполняемые вызовом метода RunSynchronously(), связаны с текущим TaskScheduler и выполняются в вызывающем потоке. Если целевой планировщик не поддерживает выполнение этой задачи в вызывающем потоке, задача будет запланирована для выполнения в расписании, а вызывающий поток будет блокироваться до завершения выполнения задачи

Зачем нужен TaskScheduler здесь, если задача будет запущена в вызывающем потоке?

Ответ 1

RunSynchronously делегирует решение о том, когда начинать задачу, текущему планировщику задач (или переданному в качестве аргумента).

Я не уверен, почему он существует (может быть, для внутреннего или устаревшего использования), но трудно представить себе полезный вариант использования в текущих версиях .NET. У @Fabjan есть возможное объяснение в его комментарии к вопросу.

RunSynchronously просит планировщик запустить его синхронно, но тогда планировщик вполне может проигнорировать подсказку и запустить его в потоке пула потоков, и ваш текущий поток будет синхронно блокироваться до его завершения.

Планировщик не должен запускать его в текущем потоке и не должен запускать его немедленно, хотя я думаю, что это произойдет с обычными планировщиками (ThreadPoolTaskScheduler и обычными планировщиками пользовательского интерфейса).

RunSynchronously также вызовет исключение, если задача уже была запущена или завершена/не выполнена (это означает, что вы не сможете использовать ее в асинхронных методах).

Этот код может прояснить другое поведение:

Wait и Result вообще не запускают задачу, они просто ждут завершения задачи в текущем потоке и блокируют его до завершения, поэтому, если мы хотим сравнить, мы можем сравнить Start и Wait с RunSynchronously:

class Scheduler : TaskScheduler
{
    protected override void QueueTask(Task task) => 
        Console.WriteLine("QueueTask");

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        Console.WriteLine("TryExecuteTaskInline");

        return false;
    }

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotImplementedException();
}

static class Program
{
    static void Main()
    {
        var taskToStart = new Task(() => { });
        var taskToRunSynchronously = new Task(() => { });

        taskToStart.Start(new Scheduler());
        taskToRunSynchronously.RunSynchronously(new Scheduler());
    }
}

Если вы попытаетесь комментировать Start или RunSynchronously и запустите код, вы увидите, что Start попытается поставить задачу в очередь в планировщике, а RunSynchronously попытается выполнить ее в оперативном режиме, а в случае неудачи (вернет false) она просто поставит ее в очередь.

Ответ 2

Сначала давайте рассмотрим этот код:

public async static Task<int> MyAsyncMethod()
{
   await Task.Delay(100);
   return 100;
}

//Task.Delay(5000).RunSynchronously();                        // bang
//Task.Run(() => Thread.Sleep(5000)).RunSynchronously();     // bang
// MyAsyncMethod().RunSynchronously();                      // bang

var t = new Task(() => Thread.Sleep(5000));
t.RunSynchronously();                                     // works

В этом примере мы попытались вызвать RunSynchronously на задачу, которая:

  • Возвращает другую задачу с результатом (задача обещания)
  • Это "горячая" задача
  • Еще одна задача обещания, созданная async await
  • "Холодная" задача с делегатом

Какие статусы у них будут после создания?

  • Ожидание активации
  • WaitingToRun
  • WaitingForActivation
  • созданный

Все "горячие" задачи создаются со статусом WaitingForActivation или WaitingToRun и связаны с планировщиком задач.

Метод RunSynchronously только знает, как работать с "холодными" задачами, которые содержат делегат и имеют статус " Created.

Заключение:

RunSynchronously метод RunSynchronously появился, когда не было "горячих" задач или они не были широко использованы и были созданы для определенной цели.

Мы могли бы использовать его в случае, если нам нужна "холодная" задача с пользовательским TaskScheduler, иначе она будет устаревшей и бесполезной.

Для запуска "горячей" задачи синхронно (чего следует избегать) мы могли бы использовать task.GetAwaiter().GetResult(). В качестве бонуса он будет возвращать исходное исключение, а не экземпляр AggregateException.