问题
Let's say I have two independent async functions (that I don't control) that create Task objects:
Task A();
Task B();
and some other non-async function
void X();
How do I construct a single Task chain that executes all of these in sequence and allows further continuations (that will execute after X) to be appended?
If I do this:
Task Sequential()
{
return A()
.ContinueWith(t => { B(); })
.ContinueWith(t => { X(); });
}
that does not work, because B will start a new Task chain. If B takes a long time to complete, X will be executed first (along with whatever else the caller of Sequential might ContinueWith on the returned Task). I need X to be the last element in a single Task chain, with the B Task as its precedent.
If I do this:
Task Sequential()
{
return A()
.ContinueWith(t => { B().ContinueWith(t => { X(); }); });
}
that only partially solves the problem, because even though A, B and X will now execute in sequential order, if the caller does Sequential().ContinueWith, that continuation will execute in parallel to B and X, not after X.
回答1:
Is there any reason not to use await? For example,
async Task Sequential()
{
await A();
await B();
X();
}
回答2:
Assuming that you cannot use async/await
as suggested in other answers (if you can, you should), there's a nifty little extension method available to cater for this scenario since the introduction of Task
in .NET 4.0: System.Threading.Tasks.TaskExtensions.Unwrap
. It takes in a Task<Task>
(or Task<Task<TResult>>
) and "flattens" it into a contiguous Task
(or Task<TResult>
respectively), which represents the completion of both the outer task, and the inner task.
Using that extension method your method can be rewritten as:
Task Sequential()
{
return A()
.ContinueWith(t => B()).Unwrap()
.ContinueWith(t => X()); // You said X() is "non-async", so no Unwrap here.
}
The resultant Task
will represent the completion of the entire sequential chain of tasks, in the expected order.
There is also the concept of "child tasks" originally devised for this very purpose in the early days of the Task Parallel Library, but it is horrendously difficult to use and requires that you have great control over how the tasks are started, which you may not have. Still, it's worth knowing about (if only for education's sake).
回答3:
There's a fairly neat way to do this using Microsoft's Reactive Framework (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();
}
If we define the methods are this:
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(); }
Then running Sequential().Wait();
produces this:
A A B B X X
回答4:
Another answer for question "How to chain independent C# tasks?"
'Infinite task chain'
Task _lastRegisteredTask = null;
var newTask = new Task(() =>
{
// do stuff
});
if (_lastRegisteredTask != null && !_lastRegisteredTask.IsCompleted)
{
_lastRegisteredTask.ContinueWith(antecedent => newTask.Start());
}
else
{
newTask.Start();
}
_lastRegisteredTask = newTask;
This can be very useful in a situation where you need to start tasks and you don't know and don't care how many and how fast they will execute but you do care that they should work in a FIFO way.
来源:https://stackoverflow.com/questions/40943380/how-to-chain-independent-c-sharp-tasks