Task.Run Etiquette Examples: Don't Use Task.Run for the Wrong Thing
• CommentsI had quite a few comments on my last post asking for more explicit examples of Correct vs. Incorrect Task.Run
usage.
First, let’s consider the “beginner’s error”. This is where the user misuses Task.Run
because they want to make their code “asynchronous” but aren’t sure how to do it.
This kind of user starts off with existing code, which usually does some kind of synchronous work (often database access or a web request).
They’ve read a bit about how async
helps in those areas, and decide to give it a spin. “Let’s see if I can figure out this async
thing. I’ll just add an async
and see what happens. Oh, I have to change the return type to Task
, too.”
“Now the compiler is complaining that I’m not using await
. OK, so what can I await? [Google-Fu] Ah, Task.Run
looks promising!”
“Hey, it worked! My UI thread is no longer blocked! Yay for async
!”
Unfortunately, this is a misuse of Task.Run
. The problem is that it’s not truly asynchronous. It’s still executing blocking work, blocking a thread pool thread the whole time the operation is in progress.
The proper approach is to change the blocking call to an asynchronous call first and then allow async
to grow from there. So, starting from the same point, we first change the blocking call to an asynchronous call. In the real world, this would be like replacing WebClient
with HttpClient
or converting your Entity Framework queries to be asynchronous. In this example, I’m replacing Thread.Sleep
with Task.Delay
.
Now we’re getting a compiler error, and we need to make the method async
.
And now we end up with a more correct implementation.
Note that this was an example of using Task.Run
for the wrong thing. To reiterate a sentence from my last post, use Task.Run
to call CPU-bound methods (from GUI threads). Do not use it just to “provide something awaitable for my async method to use”.