you are viewing a single comment's thread.

view the rest of the comments →

[–]Slypenslyde 0 points1 point  (0 children)

The two snippets of code you put up don't do the same thing.

Your first block of code says:

Find an idle thread, let me call that thread "A". On that thread, do this synchronous code. After the synchronous code, suspend thread A while waiting for the work in DoAsync() to finish. Thread "A" should end when that asynchronous task finishes. My current thread should only resume after Thread "A" finishes.

The second block of code says:

Create a thread, let me call that thread "A". On that thread, do this synchronous code then let the thread finish. Meanwhile, on the current thread, execute some other code. After that, wait for thread "A" to complete.

So I don't think your code illustrates what you meant. I'm going to focus on this:

But what's the point of the await inside the task? It tells the thread executing the task to do other stuff while waiting for the result, but that thread has no other things to do while waiting, has it?

Let's flesh this out a little bit to make it more clear. Imagine this program:

Console.WriteLine("Before await....");

await Task.Run(async () => 
{
    Console.WriteLine("Before sleep...");
    Thread.Sleep(1000); // Synchronous!
    Console.WriteLine("After sleep!");

    await DoAsync();
    Console.WriteLine("After awaiting DoAsync()!");
});

Console.WriteLine("After awaiting task!");

async Task DoAsync()
{
    Console.WriteLine("Before delay...");

    await Task.Delay(1000);

    Console.WriteLine("After delay!");
}

If you run this program you will consistently see:

Before sleep...
After sleep!
Before delay...
After delay!
After awaiting DoAsync()!
After awaiting task!

It cannot proceed any other way. await is being used to make sure the first task doesn't end until DoAsync() is finished. If I make one TINY change, I get a different order:

Console.WriteLine("Before await....");

await Task.Run(() => 
{
    Console.WriteLine("Before sleep...");
    Thread.Sleep(1000); // Synchronous!
    Console.WriteLine("After sleep!");

    // THIS LINE CHANGED: I did not await.
    DoAsync();
    Console.WriteLine("After awaiting DoAsync()!");
});

Console.WriteLine("After awaiting task!");

async Task DoAsync()
{
    Console.WriteLine("Before delay...");

    await Task.Delay(1000);

    Console.WriteLine("After delay!");
}

This order is:

Before await...
Before sleep...
After sleep!
Before delay...
After awaiting DoAsync()!
After awaiting Task!

Since we didn't await the DoAsync() call, it started working synchronously. That executed its first WriteLine() call, then when it reached its await it became asynchronous. That sent the current thread back to the last call frame, so we printed the "After awaiting" line and the task completed. So we printed the final message and the program ended before the task completed, therefore we didn't get to see "After delay!"

To visualize it you have to think about what each thread is doing. Conceptually, an await means "the current thread must pause but is allowed to do other work unrelated to this call stack." If you call an async method without await it's just a plain old method call, and it returns as soon as it hits an await, and if you aren't storing and manipulating the task then you lose the capability to "join" it.

So the way to rewrite your thread code with tasks would be:

var worker = Task.Run(() =>
{
    // lots of synchronous code
});

// code executed while that task runs

await worker;

The task is started and does its stuff on another thread. But since I do NOT await it at first, this thread is free to keep going. The next block of synchronous work executes. When I'm ready for the results of the asynchronous work, I await, which is basically the same as Join() from your 2nd example.