2016-12-03 7 views
5

Скажу, у меня есть два независимых асинхронная функция (что я не контролирую), которые создают объекты Задачи:Как связать независимые задачи C#?

Task A(); 
Task B(); 

и некоторые другие функции, не асинхронной

void X(); 

Как построить один Целевая цепочка, выполняющая все эти последовательности и позволяющая добавлять дополнительные продолжения (которые будут выполняться после X)?

Если я это сделать:

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

, что не работает, потому что B начинает новую целевую цепочку. Если B занимает много времени, X будет выполнен первым (вместе с тем, что еще может вызвать вызывающий из SequentialWith на возвращаемой Задаче). Мне нужно, чтобы X был последним элементом в одной цепочке задач, а задача B - его прецедент.

Если я делаю это:.

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

, что лишь частично решает эту проблему, потому что даже если A, B и X теперь будет выполняться в последовательном порядке, если вызывающий абонент делает Sequential() ContinueWith, что продолжение будет выполняться параллельно с B и X, а не после X.

+0

ответ Алекса так, как вы хотели бы пойти. Но для ваших целей то, что вы пытаетесь выполнить, потребует использования обещания (которое 'TaskCompletionSource ' служит). –

+1

Не является ли 'Task.WaitAll (A(), B()). ContinueWith (t => X())' вариант, если вы говорите, что 'A' и' B' являются независимыми? – VMAtm

+0

@VMAtm, вы имеете в виду 'Task.WhenAll'. –

ответ

7

Есть ли причина не использовать? Например,

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

Спасибо, это сработало! –

4

Предполагая, что вы не можете использовать async/await как предложено в другие ответы (если вы можете, вы должны), есть отличный маленький метод расширения доступны для удовлетворения этого сценария с момента введения Task в .NET 4.0: System.Threading.Tasks.TaskExtensions.Unwrap. Он принимает в Task<Task> (или) и «выравнивает» его в смежный Task (или Task<TResult> соответственно), который представляет собой завершение как внешней задачи, так и внутренней задачи.

Используя этот метод расширения вашего метод может быть переписан в виде:

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

Полученный Task будет представлять завершение всей последовательной цепочки задач, в ожидаемом порядке.

Существует также the concept of "child tasks" первоначально разработан для этой цели в первые дни библиотеки Task Parallel Library, но чудовищно трудно использовать и требует, чтобы у вас есть большой контроль над тем, как задачи начали, которые вы можете не иметь. Тем не менее, это стоит знать (если только ради образования).

2

Существует довольно аккуратный способ сделать это с использованием реактивной платформы 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(); 
} 

Если мы определим методы это:

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(); } 

Затем работает Sequential().Wait(); производит это:

 
A 
A 
B 
B 
X 
X