【问题标题】:How to Await Task.Run(action) when action is unknown未知操作时如何等待 Task.Run(action)
【发布时间】:2020-01-22 11:25:23
【问题描述】:

我有以下代码:

  static void Main(string[] args)
    {
        Run(() => LongProcess()).ContinueWith( t => Run(() => LongerProcess()));

        int count = 5;
        do
        {
            Console.WriteLine(count.ToString());
            Thread.Sleep(100);
            count++;
        } while (count < 20);

        Console.ReadKey();
    }

    private static void LongProcess()
    {

        Console.WriteLine("Long Process 1");
        Thread.Sleep(2000);
        Console.WriteLine("Long Process 2");
    }

    private static void LongerProcess()
    {
        Console.WriteLine("Longer Process 1");
        Thread.Sleep(3000);
        Console.WriteLine("Longer Process 2");
    }

    private static async Task Run(Action action)
    {

        await Task.Run(action);  //shouldn't this wait until the action is completed??
                                // how should I modify the code so that the program waits until action is done before returning?

    }

使用await Task.Run(action);,我希望程序会等到action 完成后,再继续下一步。换句话说,我期待这样的输出:

Long Process 1
Long Process 2
Longer Process 1
Longer Process 2
5
6
7
8
...
19

但是,输出是这样的

Long Process 1
5
6
7
8
...
19
Long Process 2
Longer Process 1
Longer Process 2

问题:

  1. 为什么await Task.Run 不等到action 完成?
  2. 如何更改代码以提供我想要的输出?

【问题讨论】:

    标签: c# task-parallel-library


    【解决方案1】:

    使用await 不会等待,这就是重点。如果您想等待,请使用Task.Wait

    但是,async 方法的执行只有在等待的任务完成后才会恢复(它是一个延续)。让我们看看你的async 方法:

    private static async Task Run(Action action)
    {
    
        await Task.Run(action);
        // There is nothing here
    }
    

    任务完成后没有要执行的代码...好吧,async 方法返回一个在完成后完成的任务。让我们看看你是否await...

    Run(() => LongProcess()).ContinueWith( t => Run(() => LongerProcess()));
    

    你没有。你解雇了那个任务然后忘记它。


    我想这就是你想要的:

    static async void Main(string[] args)
    {
        await Run(() => LongProcess());
        await Run(() => LongerProcess());
    
        int count = 5;
        do
        {
            Console.WriteLine(count.ToString());
            Thread.Sleep(100);
            count++;
        } while (count < 20);
    
        Console.ReadKey();
    }
    

    注意:Async Main 是 C# 7.1 功能。

    此代码将运行调用LongProcess 的任务,然后作为运行LongerProcess 的任务的延续,然后作为其余代码的延续。

    我为什么要这样做而不是仅仅同步调用这些方法。

    顺便说一句,在async 方法中,您可以使用await Task.Delay(milliseconds) 而不是Thread.Sleep(milliseconds)。优点是线程不等待。 这就像在单个执行计时器上继续。


    如果async/await 只是延续,您可能想知道为什么人们要使用它们而不是ContinueWith

    让我给你几个理由:

    • 带有async/await 的代码更易于阅读。更少的嵌套内容使代码变得模糊。您可以独立于理解线程来理解代码的作用。
    • async/await 的代码更容易编写。您只需将其编写为好像它们都是同步的,其中有一些 asyncawait 闪闪发光。您可以编写同步代码,然后担心如何使其并发。你最终也会写得更少,这意味着你的工作效率更高。
    • async/await 的代码更智能™。编译器将您的代码重写为连续状态机,允许您将await 放入语句中,而无需任何代码体操™。这延伸到异常处理。您会看到,在您的任务继续执行中,您没有处理异常。如果LongerProcess 抛出怎么办?好吧,使用async/await,您只需将await 放在try ... catch 内,它就可以做正确的事情™。

    这都是很好的关注点分离。


    您可能还想知道在什么情况下使用async/await 代替同步代码是个好主意。答案是它在处理 I/O 操作时大放异彩。与外部系统交互时,通常不会立即得到响应。例如,如果您想从磁盘读取或从网络下载。

    事实上,如果您考虑操作表单并处理其事件是 I/O 绑定操作(因为它们是)。您会发现它们是使用async/await 的好地方。特别是考虑到 UI 线程的同步上下文默认情况下会在同一线程上保持延续。当然,您可以通过使用Task.Runasync/await 与CPU 绑定操作一起使用。什么时候是个好主意?啊,是的,当你想让 UI 保持响应时。

    【讨论】:

    • 感谢您出色的解释,我不清楚的一件事是:“更少的嵌套内容使代码变得模糊。”-您是什么意思?可以说明为什么ContinueWith 会导致更多嵌套?
    • @Graviton 我在考虑嵌套表达式。这个Run(() =&gt; LongProcess()).ContinueWith( t =&gt; Run(() =&gt; LongerProcess())); 已经比这个await Run(() =&gt; LongProcess()); await Run(() =&gt; LongerProcess()); 有更多的嵌套。当然,你可以选择使 LongProcessLongerProcess 异步,然后你有 await LongProcessAsync(); await LongerProcessAsync();
    • @Graviton 话虽如此,如果你想使用ContinueWith,并且在延续中你想调用一些异步方法......那么你可以有一些任务嵌套,这可以通过简单地避免使用await。例如,请参阅C# Chained ContinueWith Not Waiting for Previous Task to Complete。请注意,非嵌套解决方案需要Wait,这意味着它在等待中浪费了一些线程。虽然await 解决方案更易于阅读并且做正确的事™。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-02
    • 2018-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多