繁体   English   中英

如何链接独立的 C# 任务?

[英]How to chain independent C# tasks?

假设我有两个独立的异步函数(我无法控制)来创建 Task 对象:

Task A();
Task B();

和其他一些非异步函数

void X();

如何构建一个按顺序执行所有这些并允许附加继续(将在 X 之后执行)的单个任务链?

如果我这样做:

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

这不起作用,因为 B 将启动一个新的任务链。 如果 B 需要很长时间才能完成,X 将首先执行(以及 Sequential 的调用者在返回的 Task 上可能 ContinueWith 的任何其他内容)。 我需要 X 作为单个任务链中的最后一个元素,并以 B 任务为先例。

如果我这样做:

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

这只能部分解决问题,因为即使 A、B 和 X 现在按顺序执行,如果调用者执行 Sequential().ContinueWith,则该继续将与 B 和 X 并行执行,而不是在 X 之后执行。

有什么理由不使用等待吗? 例如,

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

假设您不能按照其他答案中的建议使用async/await (如果可以,您应该这样做),自 .NET 4.0 中引入Task以来,有一个漂亮的小扩展方法可用于满足这种情况: System.Threading.Tasks.TaskExtensions.Unwrap 它接收一个Task<Task> (或Task<Task<TResult>> )并将其“扁平化”为一个连续的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将代表按预期顺序完成的整个顺序任务链。

还有“子任务”的概念,最初是在任务并行库的早期为此目的而设计的,但它非常难以使用,并且需要您对任务的启动方式有很大的控制,您可以没有。 尽管如此,还是值得了解的(如果只是为了教育)。

使用 Microsoft 的 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();
}

如果我们定义方法是这样的:

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

问题“如何链接独立的 C# 任务? ”的另一个答案

'无限任务链'

Task _lastRegisteredTask = null;

var newTask = new Task(() =>
{
    // do stuff
});

if (_lastRegisteredTask != null && !_lastRegisteredTask.IsCompleted)
{
    _lastRegisteredTask.ContinueWith(antecedent => newTask.Start());
}
else
{
    newTask.Start();
}

_lastRegisteredTask = newTask;

这在您需要启动任务并且您不知道也不关心它们执行的数量和速度的情况下非常有用,但您确实关心它们应该以 FIFO 方式工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM