2016-12-03 8 views
5

Powiedzmy mam dwóch samodzielnych funkcji async (że nie kontrolować), które tworzą obiekty zadanie:Jak łańcuch niezależnych zadań C#?

Task A(); 
Task B(); 

i jakąś inną funkcję niż asynchronicznej

void X(); 

Jak skonstruować jeden Łańcuch zadań, który wykonuje wszystkie te sekwencje i pozwala na dołączenie dalszych ciągów (które będą wykonywane po X)?

Gdybym to zrobić:

Task Sequential() 
{ 
    return A() 
    .ContinueWith(t => { B(); }) 
    .ContinueWith(t => { X(); }); 
} 

że nie działa, ponieważ B rozpocznie nowy łańcuch zadań. Jeśli B zajmie dużo czasu, X zostanie wykonane jako pierwsze (wraz z tym, co jeszcze wywołujący sekwenser może kontynuować w zwróconym zadaniu). Potrzebuję X jako ostatniego elementu w pojedynczym łańcuchu zadań, z zadaniem B jako jego precedensem.

Gdybym to zrobić.

Task Sequential() 
{ 
    return A() 
    .ContinueWith(t => { B().ContinueWith(t => { X(); }); }); 
} 

że tylko częściowo rozwiązuje problem, bo choć A, B i X będzie teraz wykonać w kolejności, jeśli rozmówca robi kolejny() ContinueWith, że kontynuacja będzie wykonywany równolegle z B i X, a nie po X.

+0

odpowiedź Alex jest droga, co chcesz iść. Ale dla twoich celów to, co próbujesz osiągnąć, wymagałoby użycia obietnicy (która służy do 'TaskCompletionSource '). –

+1

Czy nie jest 'Task.WaitAll (A(), B()). ContinueWith (t => X())' opcja, jeśli powiesz, że 'A' i' B' są niezależne? – VMAtm

+0

@VMAtm, masz na myśli 'Task.WhenAll'. –

Odpowiedz

7

Czy istnieje jakiś powód, dla którego nie warto czekać? Na przykład,

async Task Sequential() 
{ 
    await A(); 
    await B(); 
    X(); 
} 
+0

Dzięki, to zadziałało! –

4

Zakładając, że nie można używać async/await jak sugerowano w innych odpowiedzi (jeśli to możliwe, należy), tam fajną metodę rozszerzenia dostępne w celu zaspokojenia tego scenariusza od wprowadzenia Task w .NET 4.0: System.Threading.Tasks.TaskExtensions.Unwrap. Pobiera on Task<Task> (lub Task<Task<TResult>>) i "spłaszcza" go w sąsiednim Task (lub Task<TResult> odpowiednio), który reprezentuje zakończenie zarówno zewnętrznego zadania, jak i wewnętrznego zadania.

Stosując tę ​​metodę rozszerzenia metoda może być zapisane jako:

Task Sequential() 
{ 
    return A() 
     .ContinueWith(t => B()).Unwrap() 
     .ContinueWith(t => X()); // You said X() is "non-async", so no Unwrap here. 
} 

Powstały Task będzie reprezentować zakończenie całego sekwencyjnego łańcucha zadań, w oczekiwanej kolejności.

Istnieje również the concept of "child tasks" pierwotnie opracowany dla tego samego celu w pierwszych dniach Parallel Library zadanie, ale jest horrendalnie trudne w obsłudze i wymaga, że ​​masz dużą kontrolę nad tym, jak zadania są rozpoczęła, które maja nie mieć. Warto jednak o tym wiedzieć (choćby ze względu na edukację).

2

Istnieje dość fajny sposób, aby to zrobić za pomocą reaktywnego środowiska Microsoft (NuGet "System.Reactive").

public Task Sequential() 
{ 
    return 
    (
     from a in Observable.FromAsync(() => A()) 
     from b in Observable.FromAsync(() => B()) 
     from c in Observable.Start(() => X()) 
     select c 
    ).ToTask(); 
} 

Jeśli zdefiniujemy metody są to:

public Task A() { return Task.Run(() => { "A".Dump(); Thread.Sleep(1000); "A".Dump(); }); } 
public Task B() { return Task.Run(() => { "B".Dump(); Thread.Sleep(1000); "B".Dump(); }); } 
public void X() { "X".Dump(); Thread.Sleep(1000); "X".Dump(); } 

Następnie uruchomiony Sequential().Wait(); produkuje to:

 
A 
A 
B 
B 
X 
X 
Powiązane problemy