有几次,我发现自己编写了长时间运行的异步方法,比如轮询循环。这些方法可能如下所示:
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);
}
}
为此目的使用异步的目的是我们不需要专用的轮询线程,但逻辑(对我来说)比直接使用定时器更容易理解(也不用担心)关于重入)。
我的问题是,从同步上下文启动这样一个循环的首选方法是什么?我至少可以想到两种方法:
var pollingTask = Task.Run(async () => await this.PollLoop());
// or
var pollingTask = this.PollLoop();
在任何一种情况下,我都可以使用ContinueWith()来响应异常。我对这两种方法之间差异的主要理解是,第一个将首先在线程池线程上开始循环,而第二个将在当前线程上运行,直到第一个等待。这是真的?还有其他需要考虑的事项或更好的方法吗?
答案 0 :(得分:7)
我对这两种方法之间差异的主要理解是 第一个将首先开始在线程池线程上循环, 而第二个将在当前线程上运行,直到第一个 等待。这是真的吗?
是。异步方法在第一次等待尚未完成的等待时将其任务返回给其调用者。
按照惯例,大多数异步方法返回的速度非常快。你的也会这样做,因为await someHttpClient.GetAsync
很快就会到达。
将此异步方法的开头移到线程池上是没有意义的。它增加了开销,几乎没有延迟。它肯定无助于吞吐量或扩展行为。
在这里使用异步lambda(Task.Run(async () => await this.PollLoop())
)特别没用。它只是将PollLoop
返回的任务包含在另一层任务中。最好说Task.Run(() => this.PollLoop())
。
答案 1 :(得分:3)
我对这两种方法之间差异的主要理解是,第一种方法最初将在线程池线程上开始循环,而第二种方法将在当前线程上运行,直到第一次等待。这是真的吗?
是的,那是真的。
在您的方案中,似乎不需要使用Task.Run
,方法调用和第一个await
之间几乎没有代码,因此PollLoop()
几乎会立即返回。不必要地将任务包装在另一个任务中只会降低代码的可读性并增加开销。我宁愿使用第二种方法。
关于其他考虑因素(例如异常处理),我认为这两种方法是等效的。
为此目的使用async的目的是我们不需要专用的轮询线程,但是(对我而言)逻辑比直接使用定时器更容易理解
作为旁注,这或多或少是计时器无论如何都会做的。实际上Task.Delay
使用计时器 implemented!