Подтвердить что ты не робот

Используйте обратный вызов async с помощью Task.ContinueWith

Я пытаюсь немного поиграть с С# async/await/continuewith. Моя цель состоит в том, чтобы иметь две задачи, которые работают параллельно, хотя какая задача выполняет последовательность действий по порядку. Для этого я планировал иметь List<Task>, которые представляют 2 (или более) задачи, выполняющиеся параллельно, и использовать ContinueWith для каждого из Task Моя проблема в том, что обратный вызов в продолжении, похоже, не выполняется, пока await taskList уже возвращен.

Чтобы обобщить здесь, образец для иллюстрации того, что я ожидаю:

class Program
{
    static public async Task Test()
    {
        System.Console.WriteLine("Enter Test");
        await Task.Delay(100);
        System.Console.WriteLine("Leave Test");
    }

    static void Main(string[] args)
    {
        Test().ContinueWith(
        async (task) =>
        {
            System.Console.WriteLine("Enter callback");
            await Task.Delay(1000);
            System.Console.WriteLine("Leave callback");
        },
        TaskContinuationOptions.AttachedToParent).Wait();
        Console.WriteLine("Done with test");
    }
}

Ожидаемый результат будет

Enter Test
Leave Test
Enter callback
Leave callback
Done with test

Однако выход

Enter Test
Leave Test
Enter callback
Done with test

Есть ли способ сделать задачу, на которую вызывается ContinueWith, ждать, пока предоставленная функция завершится, прежде чем считаться выполненной? то есть.. Подождите, пока обе задачи будут завершены, исходный и тот, который возвращается ContinueWith

4b9b3361

Ответ 1

При связывании нескольких задач с использованием метода ContinueWith ваш тип возвращаемого значения будет Task<T>, тогда как T является типом возврата делегата/метода, переданного в ContinueWith.

Поскольку возвращаемый тип делегата async - это Task, вы получите Task<Task> и в конечном итоге ожидаете, что делегат async вернет вам Task, который будет выполнен после первого await.

Чтобы исправить это поведение, вам нужно использовать возвращенный Task, встроенный в ваш Task<Task>. Используйте Unwrap метод расширения, чтобы извлечь его.

Ответ 2

Когда вы выполняете программирование async, вы должны стремиться заменить ContinueWith на await следующим образом:

class Program
{
  static public async Task Test()
  {
    System.Console.WriteLine("Enter Test");
    await Task.Delay(100);
    System.Console.WriteLine("Leave Test");
  }

  static async Task MainAsync()
  {
    await Test();
    System.Console.WriteLine("Enter callback");
    await Task.Delay(1000);
    System.Console.WriteLine("Leave callback");
  }

  static void Main(string[] args)
  {
    MainAsync().Wait();
    Console.WriteLine("Done with test");
  }
}

Код с использованием await намного чище и проще в обслуживании.

Кроме того, вы не должны использовать родительские/дочерние задачи с задачами async (например, AttachedToParent). Они не были предназначены для совместной работы.

Ответ 3

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

class Program
{
    static async Task Test1()
    {
        System.Console.WriteLine("Enter Test");
        await Task.Delay(100);
        System.Console.WriteLine("Leave Test");
    }

    static async Task Test2()
    {
        System.Console.WriteLine("Enter callback");
        await Task.Delay(1000);
        System.Console.WriteLine("Leave callback");
    }

    static async Task Test()
    {
        await Test1(); // could do .ConfigureAwait(false) if continuation context doesn't matter
        await Test2();
    }

    static void Main(string[] args)
    {
        Test().Wait();
        Console.WriteLine("Done with test");
    }
}