Используйте обратный вызов 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
Ответы
Ответ 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");
}
}