Каков правильный способ запуска нескольких параллельных задач в процессе asp.net?

Думаю, я что-то не понимаю. Я думал, что Task.Yield() принудительно запускает новый поток/контекст для задачи, но после перечитывает этот ответ кажется, что он просто заставляет метод быть асинхронный. Он все равно будет в том же контексте.

Какой правильный способ - в процессе asp.net - создавать и запускать несколько задач параллельно, не вызывая тупика?

Другими словами, предположим, что у меня есть следующий метод:

async Task createFileFromLongRunningComputation(int input) { 
    //many levels of async code
}

И когда ударит определенный маршрут POST, я хочу одновременно запустить вышеуказанные методы 3 раза, немедленно вернуться, но регистрировать, когда все три выполнены.

Думаю, мне нужно добавить что-то подобное в мое действие

public IHttpAction Post() {
   Task.WhenAll(
       createFileFromLongRunningComputation(1),
       createFileFromLongRunningComputation(2),
       createFileFromLongRunningComputation(3)
   ).ContinueWith((Task t) =>
      logger.Log("Computation completed")
   ).ConfigureAwait(false);
   return Ok();

}

Что нужно сделать в createFileFromLongRunningComputation? Я думал, что Task.Yield верен, но, похоже, это не так.

Ответ 1

правильный способ отключить параллельную работу для разных потоков - использовать Task.Run в качестве предлагаемого rossipedia.

Лучшие решения для фоновой обработки в ASP.Net (где ваш AppDomain можно автоматически перерабатывать/выключать вместе со всеми вашими задачами) находятся в Скотт Гензельман и Стивен Клири блоги (например, HangFire)

Однако для достижения этого можно использовать Task.Yield вместе с ConfigureAwait(false).

Все Task.Yield это возвращает awaiter, который гарантирует, что остальная часть метода не будет выполняться синхронно (путем IsCompleted return false и OnCompleted немедленно выполнить параметр Action). ConfigureAwait(false) игнорирует SynchronizationContext и поэтому заставляет остальную часть метода выполнять поток ThreadPool.

Если вы используете оба метода вместе, вы можете убедиться, что метод async немедленно возвращает задачу, которая будет выполняться в потоке ThreadPool (например, Task.Run):

async Task CreateFileFromLongRunningComputation(int input)
{
    await Task.Yield().ConfigureAwait(false);
    // executed on a ThreadPool thread
}

Изменить: Джордж Мауэр отметил, что, поскольку Task.Yield возвращает YieldAwaitable, вы не можете использовать ConfigureAwait(false), который является методом класса Task.

Вы можете добиться чего-то подобного, используя Task.Delay с очень коротким таймаутом, поэтому он не будет синхронным, но вы не потратите много времени:

async Task CreateFileFromLongRunningComputation(int input)
{
    await Task.Delay(1).ConfigureAwait(false);
    // executed on a ThreadPool thread
}

Лучшим вариантом было бы создать YieldAwaitable, который просто игнорирует SynchronizationContext так же, как при использовании ConfigureAwait(false) делает:

async Task CreateFileFromLongRunningComputation(int input)
{
    await new NoContextYieldAwaitable();
    // executed on a ThreadPool thread
}

public struct NoContextYieldAwaitable
{
    public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); }
    public struct NoContextYieldAwaiter : INotifyCompletion
    {
        public bool IsCompleted { get { return false; } }
        public void OnCompleted(Action continuation)
        {
            var scheduler = TaskScheduler.Current;
            if (scheduler == TaskScheduler.Default)
            {
                ThreadPool.QueueUserWorkItem(RunAction, continuation);
            }
            else
            {
                Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
            }
        }

        public void GetResult() { }
        private static void RunAction(object state) { ((Action)state)(); }
    }
}

Это не рекомендация, это ответ на ваши вопросы Task.Yield.

Ответ 2

(ответ l3arnon является правильным. Этот ответ больше обсуждается, является ли подход, созданный OP хорошим.)

На самом деле вам ничего не нужно. Метод createFileFromLongRunningComputation не нуждается в чем-то особенном, просто убедитесь, что вы await используете какой-то метод асинхронизации, а ConfigureAwait(false) должен избегать тупика, предполагая, что вы не делаете ничего необычного (возможно, просто файл ввода/вывода, учитывая имя метода).

Caveat

Это рискованно. ASP.net скорее всего вытащит ковер из-под вас в этой ситуации, если задачи займут слишком много времени, чтобы закончить.

Как отметил один из комментаторов, есть лучшие способы решения этой задачи. Один из них HostingEnvironment.QueueBackgroundWorkItem (который доступен только в .NET 4.5.2 и выше).

Если длительное выполнение вычислений занимает значительно много времени, вам, вероятно, лучше не удалять его из ASP.net. В этой ситуации лучшим способом было бы использовать какую-то очередь сообщений и службу, обрабатывающую эти сообщения за пределами IIS/ASP.net.