Mr. Boy
Mr. Boy

Reputation: 63720

What does accessing a Result on a Task before calling Wait actually do?

var task = Task.Run(() => DoSomeStuff()).Result;

What happens here under the hood?

I made a tiny test:

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        var r = Task.Run( () => {Thread.Sleep(5000); return 123; }).Result;
        Console.WriteLine(r);
    }
}

It prints "123" after 5s. So does accessing any such property on Task act as a shortcut to calling Task.Wait() i.e. is this safe to do?


Previously my code called Task.Delay(5000) which returned "123" immediately. I fixed this in my question but leave this here as comments and answers reference it.

Upvotes: 0

Views: 1747

Answers (3)

Eric Lippert
Eric Lippert

Reputation: 659994

You asked two questions. First, does accessing Result implicitly cause a synchronous Wait? Yes. But the more important question is:

is this safe to do?

It is not safe to do this.

It is very easy to get into a situation where the task that you are synchronously waiting on has scheduled work to run in the future before it completes onto the thread that you just put to sleep. Now we have a situation where the thread will not wake up until the sleeping thread does some work, which it never does because it is asleep.

If you already know that the task is complete then it is safe to synchronously wait for the result. If you do not, then it is not safe to synchronously wait.

Now, you might say, suppose I know through some other means that it is safe to wait synchronously for an incomplete task. Then is it safe to wait? Well, by the assumption of the question, yes, but it still might not be smart to wait. Remember, the whole point of asynchrony is to manage resources efficiently in a world of high latency. If you are synchronously waiting for an asynchronous task to complete then you are forcing a worker to sleep until another worker is done; the sleeping worker could be doing work! The whole point of asynchrony is to avoid situations where workers go idle, so don't force them to.

An await is an asynchronous wait. It is a wait that means "wait on running the rest of the current workflow until after this task is done, but find something to do while you are waiting". We did a lot of work to add it to the language, so use it!

Upvotes: 6

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131219

The test doesn't wait for Task.Delay() so it returns immediatelly. It should be :

var r = Task.Run(async  () => { await Task.Delay(5000); return 123; }).Result;

The behavior of Result is well defined - if the task hasn't completed, it blocks until it does. Accessing other Task properties doesn't block

Upvotes: 2

canton7
canton7

Reputation: 42225

So does accessing any such property on Task act as a shortcut to calling Task.Wait()?

Yes.

From the docs:

Accessing the [Result] property's get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.

However, your test doesn't do what you think it does.

Task.Delay(..) returns a Task which completes after the specified amount of time. It doesn't block the calling thread.

So () => { Task.Delay(5000); return 123; } simply creates a new Task (which will complete in 5 seconds), then throws it away and immediately returns 123.

You can either:

  1. Block the calling thread, by doing Task.Delay(5000).Wait() (which does the same thing as Thread.Sleep(5000))
  2. Asynchronously wait for the Task returned from Task.Delay to complete: Task.Run(async () => { await Task.Delay(5000); return 123; })

Upvotes: 3

Related Questions