0

I know there are a million questions on this and I am trying their solutions but it does not seem to behave as I would expect.

With the following code:

await Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");

I get the output:

[ 5 seconds elapses ]
I'm running after Thread.Sleep
I'm running after Task.Run

Which is the same output I would get without using Task.Run

What I want is the output:

I'm running after Task.Run
[ 5 seconds elapses ]
I'm running after Thread.Sleep

Where execution of the 'parent' thread continues and the long-running task is executed 'in the background' in a way which does not affect the 'parent' thread.

Frayt
  • 1,194
  • 2
  • 17
  • 38
  • 1
    You need to remove the await keyword from the task.run, because you are awaiting the execution of the Task.Run in order to continue the execution of your program – nalnpir May 28 '19 at 12:21

3 Answers3

8

As mentioned you need to remove the await. However that turns it into a fire-and-forget task which, if the above code just happens to be in say a console app's Main then your process could terminate before the task has completed.

Good:

 // C# 7
 static async void Main(string[] args) // <--- note `async` signature
 {
     await Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running 
                 after Thread.Sleep"); });
     Console.WriteLine("I'm running after Task.Run");
 }

Bad: - fire-and-forget task that isn't awaited

 static void Main(string[] args)
 {
     Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("Spider Man: I don't wan't to go!"); });  // POOF!! 

     Console.WriteLine("I'm running after Task.Run");

     // sorry Spidy, you're dead
 }

So you might not see I'm running after Thread.Sleep at all even if you have a Thread.Sleep(). (by the way you should be using Task.Delay()). You could mitigate it with a Console.ReadKey() but that might defeat the purpose.

Fire-and-forget tasks have their uses, and can be used reasonably safely in other types of long-running apps like say WinForms but people generally don't go spawning Tasks in the process's entry point so the tasks generally run to completion without any early termination by the process. However, it's generally a good idea to use await where you can so you don't shoot yourself in the foot.

Also, your requirements of wanting things in a certain order sort of demonstrates a misunderstanding of how async/await works.

  • `Task.Run` threw me off, because I've always known that you have to `await` or `Wait()` a `Task` returned by a method, otherwise nothing will happen. However `Task.Run` is an exception to that rule: It returns a `Task` the lambda function runs without doing `await Task.Run` – Frayt May 28 '19 at 15:29
  • @Frayt _"otherwise nothing will happen"_ - incorrect. Calling an `async` method returns a [hot task, a task that has already been started](https://stackoverflow.com/a/43089445/585968). By adding `await` you ensure that the `Task` gets a chance to run to completion before the next line in the code is executed. `Task.Run()` being a _"method"_ also [returns a hot task](https://stackoverflow.com/a/43089445/585968). Use `Wait()` as a last resort. If you are calling an `async` method from a method that can itself be changed to `async`, you can avoid the `Wait()`. `Wait` defeats the purpose –  May 28 '19 at 22:46
4

One thing to keep in mind when working with await is that your code awaits objects. These objects are just like any other objects. Specifically, they can be assigned to local variables or class variables.

So, this code:

await Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");

is essentially the same as this code:

var task = Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
await task;
Console.WriteLine("I'm running after Task.Run");

In other words:

  1. Start a task running on a background thread.
  2. Asynchronously wait (await) for that task to complete.
  3. Display "I'm running after Task.Run".

So the output is expected.

What I want is the output:

I'm running after Task.Run

[ 5 seconds elapses ]

I'm running after Thread.Sleep

In that case, start the task running on the background thread, but don't await it until later:

var task = Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");
await task;
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
-3

If you want to run asynchronously you should remove the command away

        static void Main(string[] args)
        {
            Task.Run(() => { Thread.Sleep(5000); Console.WriteLine(DateTime.Now + " => I'm running after Thread.Sleep"); });
            Console.WriteLine(DateTime.Now + " => I'm running after Task.Run");
            while (true)
            {
                Thread.Sleep(500);
            }
        }

Output 28/05/2019 09:49:58 => I'm running after Task.Run 28/05/2019 09:50:03 => I'm running after Thread.Sleep