ожидайте некоторых функций и продолжайте запускать некоторый код в ожидании Task.WhenAll? - PullRequest
0 голосов
/ 08 марта 2019

Существуют следующие функции.

async Task<int> T1() { Console.WriteLine("T1"); return await Task.FromResult(1); }
async Task<string> T2() { Console.WriteLine("T2"); return await Task.FromResult("T2"); }
async Task<char> T3() { await Task.Delay(2000); Console.WriteLine("T3"); return await Task.FromResult('A'); }
async Task<string> T4() { Console.WriteLine("T4"); return await Task.FromResult("T4"); }

// U1, U2, U3, and U4 need to be run right after T1, T2, T3, and T4 respectively
void U1() { System.Threading.Thread.Sleep(1000); Console.WriteLine($"After T1"); }
void U2() { System.Threading.Thread.Sleep(4000); Console.WriteLine($"After T2"); }
void U3() { System.Threading.Thread.Sleep(1000); Console.WriteLine($"After T3"); }
void U4() { System.Threading.Thread.Sleep(1000); Console.WriteLine($"After T4"); }

// TAll() needs to be run as soon as T1, T2, T3, and T4 finished.
void TAll() { Console.WriteLine("To be run after T1, T2, T3, T4"); }

// All() runs after all functions are done.
void All() { Console.WriteLine("To be run after U1, U2, U3, U4"); }

Тем не менее, следующие звонки

var t1 = T1().ContinueWith(_ => U1());
var t2 = T2().ContinueWith(_ => U2());
var t3 = T3().ContinueWith(_ => U3());
var t4 = T4().ContinueWith(_ => U4());
await Task.WhenAll(t1, t2, t3, t4);
TAll();

All();

возвращает

T1
T2
T4
After T1
After T4
T3
After T3
After T2
To be run after T1, T2, T3, T4
To be run after U1, U2, U3, U4

Ожидаемый порядок вывода:

T1
T2
T4
After T1
After T4
T3
To be run after T1, T2, T3, T4
After T3
After T2
To be run after U1, U2, U3, U4

Ответы [ 3 ]

4 голосов
/ 08 марта 2019

Вы должны использовать async и await вместо ContinueWith. В вашем случае добавление новых async методов упростит код:

var t1 = T1();
var u1 = InvokeU1(t1);
var t2 = T2();
var u2 = InvokeU2(t2);
var t3 = T3();
var u3 = InvokeU3(t3);
var t4 = T4();
var u4 = InvokeU4(t4);

await Task.WhenAll(t1, t2, t3, t4);
TAll();

await Task.WhenAll(u1, u2, u3, u4);
All();

async Task InvokeU1(Task task) { await task; U1(); }
async Task InvokeU2(Task task) { await task; U2(); }
async Task InvokeU3(Task task) { await task; U3(); }
async Task InvokeU4(Task task) { await task; U4(); }
3 голосов
/ 08 марта 2019

Продолжение задачи на самом деле является задачей. В вашем примере вы ожидаете продолжения, поэтому "Для запуска после ..." будет зарегистрировано, когда все целевые задачи и все их продолжения выполнены.

Учтите это:

//target tasks
var t1 = T1();
var t2 = T2();
var t3 = T3();
var t4 = T4();

//continuations
var c1 = t1.ContinueWith(_ => U1());
var c2 = t2.ContinueWith(_ => U2());
var c3 = t3.ContinueWith(_ => U3());
var c4 = t4.ContinueWith(_ => U4());

await Task.WhenAll(t1, t2, t3, t4);
TAll();

await Task.WhenAll(c1, c2, c3, c4);
All();

Вывод будет соответствовать ожидаемому.

Обновление

Стивен добавил хороший совет по поводу ContinueWith, и я призываю вас его использовать. Однако независимо от того, насколько опасен ContinueWith, я попытался объяснить проблему концептуально.

0 голосов
/ 08 марта 2019

Поскольку у вас есть await Task.WhenAll(t1, t2, t3, t4);, это обеспечивает T1 (), T2 (), T3 (), T4 () и связанные с ним U1 (), U2 (), U3 (), U4 () (порядок зависит от потока sleep или task.deley, которые вы упомянули в каждом из них), чтобы завершить, прежде чем он продолжит выполнять TALL () и ALL () в последовательности. Следовательно, ниже два утверждения является последним, чтобы напечатать

To be run after T1, T2, T3, T4
To be run after U1, U2, U3, U4
...