【问题标题】:Task.Run vs. direct async call for starting long-running async methods用于启动长时间运行的异步方法的 Task.Run 与直接异步调用
【发布时间】:2014-07-13 03:28:38
【问题描述】:

有好几次,我发现自己为轮询循环之类的事情编写了长时间运行的异步方法。这些方法可能看起来像这样:

private async Task PollLoop()
{
    while (this.KeepPolling)
    {
        var response = await someHttpClient.GetAsync(...).ConfigureAwait(false);
        var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

        // do something with content

        await Task.Delay(timeBetweenPolls).ConfigureAwait(false);
    }
}

为此目的使用 async 的目的是我们不需要专用的轮询线程,但(对我而言)逻辑比直接使用计时器之类的东西更容易理解(另外,无需担心重入)。

我的问题是,从同步上下文启动此类循环的首选方法是什么?我能想到至少两种方法:

var pollingTask = Task.Run(async () => await this.PollLoop());

// or

var pollingTask = this.PollLoop();

在任何一种情况下,我都可以使用 ContinueWith() 来响应异常。我对这两种方法之间区别的主要理解是,第一种方法最初将在线程池线程上开始循环,而第二种方法将在当前线程上运行,直到第一次等待。这是真的?是否有其他需要考虑或尝试更好的方法?

【问题讨论】:

  • 在第一个中我看到了 3 个任务.. 1) Task.Run 2) 因为 await(由编译器生成) 3) PollLoop 本身。另一方面,第二个只有 1 个任务。
  • 如果将第一个任务版本减少到2个,可以更好地看到答案:Task.Run(() => this.PollLoop().Wait())
  • 如果这是一个服务器端代码,你就不需要Task.Run
  • 但是如果这个任务有很多代码并且是从windows服务启动的,服务启动会等待await...而且服务运行可能需要很长时间..所以a诀窍就是把await Task.Delay(10);放在开头

标签: c# task-parallel-library async-await


【解决方案1】:

我对这两种方法区别的主要理解是 第一个最初将在线程池线程上开始循环, 而第二个将在当前线程上运行,直到第一个 等待。这是真的吗?

是的。异步方法在尚未完成的可等待对象的第一次等待时将其任务返回给调用者。

按照惯例,大多数异步方法都很快返回。你的也可以,因为await someHttpClient.GetAsync 会很快到达。

将这个异步方法的开头移到线程池是没有意义的。它增加了开销并且几乎没有节省延迟。它当然无助于吞吐量或扩展行为。

在这里使用异步 lambda (Task.Run(async () => await this.PollLoop())) 特别没用。它只是将PollLoop 返回的任务与另一层任务包装在一起。最好说Task.Run(() => this.PollLoop())

【讨论】:

  • 其实Task.Run(() => this.PollLoop())也没什么用。 this.PollLoop() 就够了……
  • @EZI 有点像。他特别问这个问题,我在回答中讨论了这个问题。
  • 您讨论它但说Task.Run(() => this.PollLoop()) 会更好,这会创建一个冗余任务araound PollLoop。在没有等待的情况下调用 PollLoop(); 将已经在任务中运行。
  • @EZI 好吧,一级冗余比二级冗余好! :)
【解决方案2】:

我对这两种方法之间区别的主要理解是,第一种方法最初将在线程池线程上开始循环,而第二种方法将在当前线程上运行,直到第一次等待。这是真的吗?

是的,没错。

在您的场景中,似乎不需要使用Task.Run,但在方法调用和第一个await 之间几乎没有代码,因此PollLoop() 几乎会立即返回。不必要地将一个任务包装在另一个任务中只会降低代码的可读性并增加开销。我宁愿使用第二种方法。

关于其他考虑(例如异常处理),我认为这两种方法是等效的。

为此目的使用 async 的目的是我们不需要专门的轮询线程,但是(对我而言)逻辑比直接使用计时器之类的东西更容易理解

作为旁注,这或多或少是计时器会做的事情。事实上Task.Delay implemented 使用计时器!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    相关资源
    最近更新 更多