0

This is what AsyncMethods class looks like:

public class AsyncMethods
{
    public static async Task<double> GetdoubleAsync()
    {
        Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(1000);
        return 80d;
    }
    public static async Task<string> GetStringAsync()
    {
        Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(1000);
        return "async";
    }
    public static async Task<DateTime> GetDateTimeAsync()
    {
        Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(1000);
        return DateTime.Now;
    }
}

This what my main method looks like:

static void Main(string[] args)
{
    while (Console.ReadLine() != "exit")
    {
        Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
        DateTime dt = DateTime.Now;
        var res = GetStuffAsync().Result;
        var ts = DateTime.Now - dt;
        Console.WriteLine(res);
        Console.WriteLine("Seconds taken: " + ts.Seconds + " milliseconds taken: " + ts.Milliseconds);
    }
    Console.ReadLine();
    return;
}
static async Task<object> GetStuffAsync()
{
    var doubleTask = AsyncMethods.GetdoubleAsync();
    var StringTask = AsyncMethods.GetStringAsync();
    var DateTimeTask = AsyncMethods.GetDateTimeAsync();

    return new
    {
        _double = await doubleTask,
        _String = await StringTask,
        _DateTime = await DateTimeTask,
    };
}

As it can be seen in each method i added a delay of 1 second. Here is the output:

Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
{ _double = 80, _String = async, _DateTime = 2/15/2017 4:32:00 AM }
Seconds taken: 1 milliseconds taken: 40

Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
{ _double = 80, _String = async, _DateTime = 2/15/2017 4:32:03 AM }
Seconds taken: 1 milliseconds taken: 16

Now i have 2 questions:

  1. How come everything happened on a single thread?
  2. Why was the Delay only 1 second when i waited 3 seconds?
Expert Novice
  • 1,943
  • 4
  • 22
  • 47
  • Seriously, VS didn't warn you about calling asynchronous code synchronously? – dragonfly02 Feb 14 '17 at 23:32
  • If 3 tasks run in parallel and each task have a delay of 1 second then the total delay is 1 second. Why did you expected a 3 seconds delay? – juanreyesv Feb 14 '17 at 23:32
  • @juanreyesv - See the threadids they are all the same. How are they running parallely on a single thread? – Expert Novice Feb 14 '17 at 23:47
  • @ExpertNovice please see this answer http://stackoverflow.com/a/27265877 and the updated MSDN link https://msdn.microsoft.com/en-us/library/mt674882.aspx – juanreyesv Feb 16 '17 at 01:35
  • Quote from above MSDN link: _The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available._ – juanreyesv Feb 16 '17 at 01:41

4 Answers4

12

First off: if you have two questions please ask two questions. Don't put two questions in one question.

How come everything happened on a single thread?

That's the wrong question to ask. The correct question is: why do you think anything should happen on a second thread?

Here, I'll give you a task: wait five minutes, and then check your email. While you're waiting, make a sandwich. Did you have to hire someone to either do the waiting or make the sandwich? Obviously not. Threads are workers. There's no need to hire a worker if the job can be done by one worker.

The whole point of await is to avoid going to extra threads if you don't need to. In this case you don't need to.

Why was the Delay only 1 second when i waited 3 seconds?

Compare these two workflows.

  • Wait five minutes; while you're waiting, make a sandwich
  • then check your email
  • then wait five minutes; while you're waiting, make a sandwich
  • then check your email
  • then wait five minutes; while you're waiting, make a sandwich
  • then check your email

If you execute that workflow, you'll wait a total of fifteen minutes.

The workflow you wrote was:

  • Wait five minutes
  • simultaneously, wait five minutes
  • simultaneously, wait five minutes
  • while you're waiting, make a sandwich
  • then check your email

You only wait five minutes with that workflow; all the delays happen at the same time.

Do you see how you wrote your program incorrectly now?

The key insight to understand here is that an await is a point in a program where the continuation of the await is delayed until after the awaited task completes.

If you don't put in an await, the program continues by itself without waiting. That's the meaning of await.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Always "interesting" examples in your answers! – dragonfly02 Feb 14 '17 at 23:26
  • @Eric Lippert: The whole point of writing this test program was to see the behavior of async versus threading. The question that is still rattling my brain is how did 1+1+1 seconds timeslice get completed in a single thread? Rest what you said about asyncrony is what made me try out this example. Joe White's answer made things a bit clearer for me. – Expert Novice Feb 15 '17 at 00:29
  • Wow, amazing answer again. Your analogies about the code are best I ever read. – VMAtm Feb 15 '17 at 00:29
  • @ExpertNovice, the `await` exits immediately if the awaitable doesn't already finish. That's why the delays are simultaneous. – VMAtm Feb 15 '17 at 00:31
  • @ExpertNovice: Those questions I asked weren't rhetorical. Somehow you manage to do workflows like "work on my taxes, oh, I need a figure I haven't got, while I'm waiting for the form to arrive in the mail, put some toast in the toaster, while I'm waiting for the toast, feed the cat..." without hiring other workers. If you can do a workflow without hiring workers, you can do that workflow without hiring threads. Threads are workers. – Eric Lippert Feb 15 '17 at 00:32
  • @ExpertNovice: Similarly, you can do workflows like "set three alarm clocks each to ring five minutes from now, and while I'm waiting for the alarm clocks to ring, do some pushups, and after they ring, mow the lawn". Did you need to hire any workers to set your alarm clocks or wait for them to ring? No. Is there some magic that causes three alarm clocks each set for five minutes from now to wait fifteen minutes? No. You set three clocks each to go off five minutes from now, and they all do. – Eric Lippert Feb 15 '17 at 00:35
  • @EricLippert: What about Joe White's statement - here `If you checked the thread ID after the await Task.Delay call, you would probably find that the continuations ran on different threads` - and it happened exactly that way - the thread ids were different. The actual execution did happen on different threads - logically in the current example since i am waiting 1 second each whether async or sync its impossible to do 3 tasks simultaneously on a single thread is what i mean. – Expert Novice Feb 15 '17 at 15:44
  • @ExpertNovice: You're not listening to me. It is *completely possible* to do three tasks asynchronously on one thread, just as it is *completely possible* for you to make bacon and eggs and toast and check your mail and do your taxes without hiring any workers to do that. The whole point of asynchrony is to *avoid* moving things off the main thread if you can. – Eric Lippert Feb 15 '17 at 17:40
  • 2
    @ExpertNovice: Now, the fact that the *continuations* may be scheduled to different threads is a consequence of the fact that you're building a console application. Console applications don't have a message loop to coordinate asynchronous tasks, and so there are scenarios in which the console app is forced to assign continuations out to other threads. If you were to write instead a WPF or WinForms program you would see that the asynchronous continuations resume on the UI thread. If you're learning about async, don't write console apps. Write GUI apps. – Eric Lippert Feb 15 '17 at 17:42
  • "the fact that the continuations may be scheduled to different threads is a consequence of the fact that you're building a console application.": Is it then useless to call `ConfigureAwait(false)` in a console application? What about in a windows service? – Luca Cremonesi Feb 15 '17 at 23:22
  • @LucaCremonesi It is indeed useless. `ConfigureAwait` attempts to capture the current `SynchronizationContext`, or possibly a non-default task scheduler. The former will be null for a console app (at least by default) so the standard "schedule continuations where we can" behavior is going to apply despite the method call. – dlev Feb 16 '17 at 06:50
2

They all start on the same thread. When you call your three Async methods in sequence, they all execute synchronously up until the first await call. (After the await, they become state machines that pick up where they left off whenever they get scheduled. If you checked the thread ID after the await Task.Delay call, you would probably find that the continuations ran on different threads -- at least here in a console app.)

As for why it's only delaying 1 second... that's what you're telling it to do. You've got three async tasks, all running simultaneously, each delaying for one second. You're not saying "[a]wait until the first task is done before starting the second" -- in fact you're carefully doing the opposite, starting all three and then awaiting all three -- so they run in parallel.

Joe White
  • 94,807
  • 60
  • 220
  • 330
0

Your Console.WriteLine() calls in GetdoubleAsync(), GetStringAsync(), and GetDateTimeAsync() happened in the calling thread because they happened before the first continuation.

Your await Task.Delay() calls yielded the thread back to the calling code.

When the task returned by Task.Delay() completed, the continuation on those Tasks returned their values and set their tasks as completed.

This allowed your 3 awaits (in sequential, synchronous order) in GetStuffAsync() to return. Each one had to wait 1 second before marked as completed, but they were yielding and happening at the same time.

I think you are looking for System.Threading.Tasks.Parallel to do things at the same time. Async...await is useful for yielding threads.

Tim
  • 5,940
  • 1
  • 12
  • 18
0

You're starting all your tasks at the same time so they're all going to run in parallel, not in sequence. That's why everything completes after 1000 milliseconds.

Additionally, async doesn't create new threads, it uses the current thread asynchronously. You can see this kind of behaviour in async javascript (which is a single threaded environment) or coroutines in Unity3D. They both allow async behaviour without threads.

So each of your tasks is being run on the same thread and completes in 1 second.

Soviut
  • 88,194
  • 49
  • 192
  • 260