it-swarm.com.ru

Ожидание нескольких задач с разными результатами

У меня есть 3 задачи:

private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}

Все они должны быть запущены, прежде чем мой код сможет продолжить работу, и мне нужны результаты каждого из них. Ни один из результатов не имеет ничего общего друг с другом

Как мне позвонить и дождаться завершения 3 заданий, а затем получить результаты?

177
BahaiResearch.com

После того, как вы используете WhenAll, вы можете получить результаты по отдельности с помощью await:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Вы также можете использовать Task.Result (поскольку к этому моменту вы знаете, что все они успешно завершены). Тем не менее, я рекомендую использовать await, потому что это совершенно правильно, в то время как Result может вызвать проблемы в других сценариях.

303
Stephen Cleary

Просто await три задачи отдельно, после запуска их всех.

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;
72
Servy

Если вы используете C # 7, вы можете использовать удобный метод-обертку, как этот ...

public static class TaskEx
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        await Task.WhenAll(task1, task2);
        return (task1.Result, task2.Result);
    }
}

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

var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
29
Joel Mueller

Вы можете хранить их в задачах, а затем ждать их всех:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;
10
Reed Copsey

Учитывая три задачи - FeedCat(), SellHouse() и BuyCar(), есть два интересных случая: либо все они завершаются синхронно (по какой-то причине, возможно, кеширование или ошибка), либо нет.

Допустим, у нас есть, из вопроса:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();
    // what here?
}

Теперь простой подход будет следующим:

Task.WhenAll(x, y, z);

но ... это не удобно для обработки результатов; мы обычно хотели бы await что:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    await Task.WhenAll(x, y, z);
    // presumably we want to do something with the results...
    return DoWhatever(x.Result, y.Result, z.Result);
}

но это приводит к большим накладным расходам и выделяет различные массивы (включая массив params Task[]) и списки (внутри). Это работает, но это не великое ИМО. Во многих случаях проще использовать операцию async и просто await каждая по очереди:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    // do something with the results...
    return DoWhatever(await x, await y, await z);
}

Вопреки некоторым комментариям выше, использование await вместо Task.WhenAll делает без разницы способ выполнения задач (одновременно, последовательно и т.д.). На самом высоком уровне Task.WhenAllпредшествующий хорошая поддержка компилятора для async/await, и была полезна , когда этих вещей не было , Это также полезно, когда у вас есть произвольный массив задач, а не 3 дискретных задачи.

Но: у нас по-прежнему есть проблема, состоящая в том, что async/await генерирует много шума компилятора для продолжения. Если существует вероятность того, что задачи могут фактически завершиться синхронно, то мы можем оптимизировать это, построив синхронный путь с асинхронным отступлением:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    if(x.Status == TaskStatus.RanToCompletion &&
       y.Status == TaskStatus.RanToCompletion &&
       z.Status == TaskStatus.RanToCompletion)
        return Task.FromResult(
          DoWhatever(a.Result, b.Result, c.Result));
       // we can safely access .Result, as they are known
       // to be ran-to-completion

    return Awaited(x, y, z);
}

async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
    return DoWhatever(await x, await y, await z);
}

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

Дополнительные вещи, которые применяются здесь:

  1. в недавнем C # общий шаблон для async альтернативного метода обычно реализуется как локальная функция:

    Task<string> DoTheThings() {
        async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        Task<Cat> x = FeedCat();
        Task<House> y = SellHouse();
        Task<Tesla> z = BuyCar();
    
        if(x.Status == TaskStatus.RanToCompletion &&
           y.Status == TaskStatus.RanToCompletion &&
           z.Status == TaskStatus.RanToCompletion)
            return Task.FromResult(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
    
  2. предпочтение от ValueTask<T> до Task<T>, если есть хорошие шансы когда-либо полностью синхронно с множеством различных возвращаемых значений:

    ValueTask<string> DoTheThings() {
        async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        ValueTask<Cat> x = FeedCat();
        ValueTask<House> y = SellHouse();
        ValueTask<Tesla> z = BuyCar();
    
        if(x.IsCompletedSuccessfully &&
           y.IsCompletedSuccessfully &&
           z.IsCompletedSuccessfully)
            return new ValueTask<string>(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
    
  3. если возможно, предпочтите IsCompletedSuccessfullyStatus == TaskStatus.RanToCompletion; теперь он существует в .NET Core для Task и везде для ValueTask<T>

8
Marc Gravell

Если вы пытаетесь зарегистрировать все ошибки, убедитесь, что вы сохранили строку Task.WhenAll в своем коде, многие комментарии предлагают удалить ее и дождаться выполнения отдельных задач. Task.WhenAll действительно важно для обработки ошибок. Без этой строки вы потенциально оставляете свой код открытым для ненаблюдаемых исключений.

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Представьте, что FeedCat выдает исключение в следующем коде:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

В этом случае вы никогда не будете ждать ни houseTask, ни carTask. Здесь есть 3 возможных сценария:

  1. SellHouse уже успешно завершен, когда FeedCat не удалось. В этом случае вы в порядке.

  2. SellHouse не завершен и завершается с ошибкой в ​​какой-то момент. Исключение не наблюдается и будет переброшено в поток финализатора.

  3. SellHouse не является полным и содержит внутри него. В случае, если ваш код работает в ASP.NET, SellHouse потерпит неудачу, как только некоторые из них будут завершены. Это происходит из-за того, что вы в основном включили вызов и забыли, что контекст синхронизации был потерян, как только FeedCat вышел из строя.

Вот ошибка, которую вы получите для case (3):

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()<---

В случае (2) вы получите похожую ошибку, но с оригинальной трассировкой стека исключений.

Для .NET 4.0 и более поздних версий вы можете перехватывать ненаблюдаемые исключения с помощью TaskScheduler.UnobservedTaskException. Для .NET 4.5 и более поздних версий ненаблюдаемые исключения по умолчанию проглатываются, а для .NET 4.0 ненаблюдаемое исключение приведет к сбою вашего процесса.

Подробнее здесь: Обработка исключений задач в .NET 4.5

3
samfromlv

Вы можете использовать Task.WhenAll, как уже упоминалось, или Task.WaitAll, в зависимости от того, хотите ли вы, чтобы поток ожидал. Взгляните на ссылку для объяснения обоих.

WaitAll vs WhenAll

2
christiandev

Используйте Task.WhenAll и дождитесь результатов:

var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar; 
//as they have all definitely finished, you could also use Task.Value.
2
It'sNotALie.
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());

если вы хотите получить доступ к Cat, вы делаете это:

var ct = (Cat)dn[0];

Это очень просто и очень полезно для использования, нет необходимости искать сложное решение.

1
taurius

Предупреждение вперед

Просто быстрое посещение тех, кто посещает этот и другие подобные потоки, ищущие способ распараллелить EntityFramework, используя набор инструментов async + await + task: Шаблон, показанный здесь, тем не менее, когда дело доходит до В особой снежинке EF вы не достигнете параллельного выполнения до тех пор, пока не будете использовать отдельный (новый) экземпляр db-context-instance внутри каждого задействованного вызова * Async ().

Такого рода вещи необходимы из-за внутренних конструктивных ограничений ef-db-context, которые запрещают параллельное выполнение нескольких запросов в одном экземпляре ef-db-context.


С учетом уже предоставленных ответов это способ убедиться, что вы собираете все значения, даже если одна или несколько задач приводят к исключению:

  public async Task<string> Foobar() {
    async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
        return DoSomething(await a, await b, await c);
    }

    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        if (carTask.Status == TaskStatus.RanToCompletion //triple
            && catTask.Status == TaskStatus.RanToCompletion //cache
            && houseTask.Status == TaskStatus.RanToCompletion) { //hits
            return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
        }

        cat = await catTask;
        car = await carTask;
        house = await houseTask;
        //or Task.AwaitAll(carTask, catTask, houseTask);
        //or await Task.WhenAll(carTask, catTask, houseTask);
        //it depends on how you like exception handling better

        return Awaited(catTask, carTask, houseTask);
   }
 }

Альтернативная реализация, которая имеет более или менее одинаковые характеристики производительности, может быть:

 public async Task<string> Foobar() {
    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
        car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
        house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);

        return DoSomething(cat, car, house);
     }
 }
0
XDS