Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.Run vs. direct async call for starting long-running async methods

Several times, I have found myself writing long-running async methods for things like polling loops. These methods might look something like this:

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);
    }
}

The goal of using async for this purpose is that we don't need a dedicated polling thread and yet the logic is (to me) easier to understand than using something like a timer directly (also, no need to worry about reentrance).

My question is, what is the preferred method for launching such a loop from a synchronous context? I can think of at least 2 approaches:

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

// or

var pollingTask = this.PollLoop();

In either case, I can respond to exceptions using ContinueWith(). My main understanding of the difference between these two methods is that the first will initially start looping on a thread-pool thread, whereas the second will run on the current thread until the first await. Is this true? Are there other things to consider or better approaches to try?

like image 835
ChaseMedallion Avatar asked Jul 12 '14 19:07

ChaseMedallion


2 Answers

My main understanding of the difference between these two methods is that the first will initially start looping on a thread-pool thread, whereas the second will run on the current thread until the first await. Is this true?

Yes. An async method returns its task to its caller on the first await of an awaitable that is not already completed.

By convention most async methods return very quickly. Yours does as well because await someHttpClient.GetAsync will be reached very quickly.

There is no point in moving the beginning of this async method onto the thread-pool. It adds overhead and saves almost no latency. It certainly does not help throughput or scaling behavior.

Using an async lambda here (Task.Run(async () => await this.PollLoop())) is especially useless. It just wraps the task returned by PollLoop with another layer of tasks. it would be better to say Task.Run(() => this.PollLoop()).

like image 119
usr Avatar answered Nov 08 '22 20:11

usr


My main understanding of the difference between these two methods is that the first will initially start looping on a thread-pool thread, whereas the second will run on the current thread until the first await. Is this true?

Yes, that's true.

In your scenario, there seem to be no need for using Task.Run though, there's practically no code between the method call and the first await, and so PollLoop() will return almost immediately. Needlessly wrapping a task in another task only makes the code less readable and adds overhead. I would rather use the second approach.

Regarding other considerations (e.g. exception handling), I think the two approaches are equivalent.

The goal of using async for this purpose is that we don't need a dedicated polling thread and yet the logic is (to me) easier to understand than using something like a timer directly

As a side-note, this is more or less what a timer would do anyway. In fact Task.Delay is implemented using a timer!

like image 33
dcastro Avatar answered Nov 08 '22 21:11

dcastro