有好几次,我发现自己在为轮询循环之类的事情编写长时间运行的异步方法。这些方法可能看起来像这样:
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((来响应异常。我对这两种方法之间的区别的主要理解是,第一种方法最初将在线程池线程上开始循环,而第二种方法将在当前线程上运行,直到第一种方法等待。这是真的吗?是否还有其他事情需要考虑或更好的方法可以尝试?
我对这两种方法之间的区别的主要理解是第一个将最初在线程池线程上开始循环,而第二个将在当前线程上运行,直到第一个等候这是真的吗?
是的。异步方法在尚未完成的awaitable的第一次等待时将其任务返回给调用方。
按照惯例,大多数异步方法返回得非常快。你的也一样,因为await someHttpClient.GetAsync
会很快到达。
将这个异步方法的开头移到线程池中是没有意义的。它增加了开销,几乎不节省延迟。它肯定无助于吞吐量或扩展行为。
在这里使用异步lambda(Task.Run(async () => await this.PollLoop())
(尤其无用。它只是用另一层任务包装PollLoop
返回的任务。最好说CCD_ 4。
我对这两种方法之间的区别的主要理解是,第一种方法最初将在线程池线程上开始循环,而第二种方法将在当前线程上运行,直到第一种方法等待。这是真的吗?
是的,这是真的。
不过,在您的场景中,似乎没有必要使用Task.Run
,方法调用和第一个await
之间实际上没有代码,因此PollLoop()
几乎会立即返回。无需将一个任务包装到另一个任务中只会降低代码的可读性并增加开销。我宁愿使用第二种方法。
关于其他考虑因素(例如异常处理(,我认为这两种方法是等效的。
为此目的使用async的目标是,我们不需要专用的轮询线程,但逻辑(对我来说(比直接使用计时器更容易理解
顺便说一句,这或多或少是计时器无论如何都会做的事情。事实上,Task.Delay
是使用计时器实现的!