为什么使用ContinueWith顺序运行任务不起作用?

时间:2019-12-22 14:58:49

标签: c# task-parallel-library

我的目标是在“ Task1”之后启动“ Task2”。起初,我在下面编写了类似“ Code1”的代码,但是没有用(Task2在Task1完成之前就启动了)。因此,我搜索了Stackoverflow,并按照建议the existing answer修改了下面的代码,例如“ Code2”。我想知道为什么“ Code1”不起作用。

代码1

    static void Main(string[] args)
    {
        var p = new Program();
        p.Test2();
        Console.ReadKey();
    }

    void Test2()
    {
        Task.Factory.StartNew(async () =>
        {
            await Task1();
        }).ContinueWith((t) => {
            Task2();
        });
    }

    async Task Task1()
    {
        Debug.WriteLine("Task 1 starting....");
        await LongTask();
        Debug.WriteLine("Task 1 done");
    }

    Task LongTask()
    {
        return Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
        });            
    }

    void Task2()
    {
        Debug.WriteLine("Task 2");
    }

代码2

        Task.Factory.StartNew(async () =>
        {
            await Task1();
            Task2();
        }).ContinueWith((t) => {
            //Task2();
        });

2 个答案:

答案 0 :(得分:2)

因为当您运行Task.Factory.StartNew(async () => ...之类的任务时,它将返回Task<Task>(任务的任务)。并且第一个任务终止而无需等待内部Task

为防止这种情况,您可以使用Unwrap方法:

Task.Factory.StartNew(async () =>
{
    await Task1();
})
.Unwrap()
.ContinueWith((t) => {
    Task2();
});

StartNew将返回Task<Task>,看来您想在内部任务完成后执行继续。这就是为什么我们需要Unwrap。 它将外部任务返回的内部任务“解包”。在任务上调用Unwrap会给您带来一个新任务(我们通常将其称为代理),它代表内部任务的最终完成。然后,我们在内部任务中添加了延续性。

但是,由于Task.Run会自动进行解包,因此您可以在这种情况下使用Task.Run

Task.Run(async () =>
{
    await Task1();
})
.ContinueWith((t) => {
    Task2();
});

这可以简化为:

Task.Run(async () =>
{
    await Task1();
    Task2();
});

有关Task.RunTask.Factory.StartNew之间差异的详细信息:

从.Net团队的工程师Stephen Toub中了解更多here。他们决定在Unwrap的情况下添加Task.Run

  

因为我们希望这种情况对于人们来说很普遍,所以他们希望卸载工作   到ThreadPool,并为了使用async/await进行这项工作,我们决定   将这种展开功能嵌入Task.Run中。

顺便说一句,正如Stephen所建议的,在大多数情况下,请尝试使用Task.Run,但这绝不会淘汰Task.Factory.StartNew,仍然有一些地方必须使用Task.Factory.StartNew

  

Task.Factory.StartNew仍然有许多重要的功能(尽管更高级)   用途。您可以控制TaskCreationOptions的任务方式   表现良好。您可以控制任务应在何处进行调度   排队并运行。您可以使用接受对象的重载   状态,对于性能敏感的代码路径可以用来避免   关闭和相应的分配。对于简单的情况,   不过Task.Run是你的朋友。

答案 1 :(得分:1)

String不理解异步lambda。对于 String test = "test2"; System.out.println(test.endsWith("[0-9]")); //false System.out.println(test.matches(".*[0-9]$")); //true ,您的lambda只是函数返回Task.Factory.StartNew。它不会自动等待该任务。另外,请注意,由于现代C#难以正确使用,因此不建议使用Task.Factory.StartNewTask(请阅读Stephen Cleary的“ StartNew是危险的”)。只有在没有它们的情况下,才应使用Task.Factory.StarNewContinueWith

相反,您可以再使用async / await一次:

Task.Factory.StarNew

ContinueWith

async Task Test2()
{
    await Task1();
    Task2();
}

如果要确保在后台线程上启动异步Task.Run ,则第二种方法可能会很方便。