86

I'm starting to learn about async / await in C# 5.0, and I don't understand it at all. I don't understand how it can be used for parallelism. I've tried the following very basic program:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task task1 = Task1();
            Task task2 = Task2();

            Task.WaitAll(task1, task2);

            Debug.WriteLine("Finished main method");
        }

        public static async Task Task1()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
            Debug.WriteLine("Finished Task1");
        }

        public static async Task Task2()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
            Debug.WriteLine("Finished Task2");
        }

    }
}

This program just blocks on the call to Task.WaitAll() and never finishes, but I am not understanding why. I'm sure I'm just missing something simple or just don't have the right mental model of this, and none of the blogs or MSDN articles that are out there are helping.

2

5 Answers 5

78

I recommend you start out with my intro to async/await and follow-up with the official Microsoft documentation on TAP.

As I mention in my intro blog post, there are several Task members that are holdovers from the TPL and have no use in pure async code. new Task and Task.Start should be replaced with Task.Run (or TaskFactory.StartNew). Similarly, Thread.Sleep should be replaced with Task.Delay.

Finally, I recommend that you do not use Task.WaitAll; your Console app should just Wait on a single Task which uses Task.WhenAll. With all these changes, your code would look like:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    public static async Task MainAsync()
    {
        Task task1 = Task1();
        Task task2 = Task2();

        await Task.WhenAll(task1, task2);

        Debug.WriteLine("Finished main method");
    }

    public static async Task Task1()
    {
        await Task.Delay(5000);
        Debug.WriteLine("Finished Task1");
    }

    public static async Task Task2()
    {
        await Task.Delay(10000);
        Debug.WriteLine("Finished Task2");
    }
}
3
  • Thank you for your response Stephen. Can you give me the TL;DR of why an await Task.WhenAll(...) is preferable to Task.WaitAll() ? Further, can you provide me a link to any documentation that explains how APIs like WPF and the Windows Runtime know how to execute event handlers asynchronously ? Commented Jan 6, 2013 at 1:39
  • 1
    Blocking on tasks can lead to deadlock (links to my blog). In your particular case, it would be fine, since a Console Main method is an exception to the "don't block" guideline. But I prefer to separate out the exceptional code (using Wait) from any logic, which I move to MainAsync. It's just that much less of a chance that you'll deadlock if/when you copy/paste this code into a GUI or ASP.NET app. Commented Jan 6, 2013 at 1:42
  • 2
    Asynchronous event handlers make use of the current SynchronizationContext. WPF (and I believe WinRT) do not have to do anything special; their existing SynchronizationContext is sufficient for async event handlers to behave correctly. I explain the context capture/resume in my intro post, and I also have an MSDN article that goes into SynchronizationContext in detail if you find it interesting. Commented Jan 6, 2013 at 1:57
18

Understand C# Task, async and await

C# Task

Task class is an asynchronous task wrapper. Thread.Sleep(1000) can stop a thread running for 1 second. While Task.Delay(1000) won't stop the current work. See code:

public static void Main(string[] args){
    TaskTest();
}
private static void TaskTest(){
     Task.Delay(5000);
     System.Console.WriteLine("task done");
}

When running," task done" will show up immediately. So I can assume that every method from Task should be asynchronous. If I replace TaskTest () with Task.Run(() =>TaskTest()) task done won't show up at all until I append a Console.ReadLine(); after the Run method.

Internally, Task class represent a thread state In a State Machine. Every state in state machine have several states such as Start, Delay, Cancel, and Stop.

async and await

Now, you may wondering if all Task is asynchronous, what is the purpose of Task.Delay ? next, let's really delay the running thread by using async and await

public static void Main(string[] args){
     TaskTest();
     System.Console.WriteLine("main thread is not blocked");
     Console.ReadLine();
}
private static async void TaskTest(){
     await Task.Delay(5000);
     System.Console.WriteLine("task done");
}

async tell caller, I am an asynchronous method, don't wait for me. await inside the TaskTest() ask for waiting for the asynchronous task. Now, after running, program will wait 5 seconds to show the task done text.

Cancel a Task

Since Task is a state machine, there must be a way to cancel the task while task is in running.

static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static void Main(string[] args){
    TaskTest();
    System.Console.WriteLine("main thread is not blocked");
    var input=Console.ReadLine();
    if(input=="stop"){
          tokenSource.Cancel();
          System.Console.WriteLine("task stopped");
     }
     Console.ReadLine();
}
private static async void TaskTest(){
     try{
          await Task.Delay(5000,tokenSource.Token);
     }catch(TaskCanceledException e){
          //cancel task will throw out a exception, just catch it, do nothing.
     }
     System.Console.WriteLine("task done");
}

Now, when the program is in running, you can input "stop" to cancel the Delay task.

0
14

Your tasks never finish because they never start running.

I would Task.Factory.StartNew to create a task and start it.

public static async Task Task1()
{
  await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
  Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
  await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
  Debug.WriteLine("Finished Task2");
}

As a side note, if you're really just trying to pause in a async method, there's no need to block an entire thread, just use Task.Delay

public static async Task Task1()
{
  await Task.Delay(TimeSpan.FromSeconds(5));
  Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
  await Task.Delay(TimeSpan.FromSeconds(10));
  Debug.WriteLine("Finished Task2");
}
3
  • 1
    Why create new tasks at all? Commented Jan 6, 2013 at 0:23
  • 1
    @DavidHeffernan hadn't gotten a chance to add that to my answer yet, but I assumed that Alex perhaps has something more complicated in mind.
    – MerickOWA
    Commented Jan 6, 2013 at 0:24
  • @MerickOWA Yes, you're right, I did have something more complicated in mind. I wanted to simulate threads that block on IO and take a long time, and aren't purely CPU bound. Commented Jan 6, 2013 at 1:37
9

Async and await are markers which mark code positions from where control should resume after a task (thread) completes. Here's a detail youtube video which explains the concept in a demonstrative manner http://www.youtube.com/watch?v=V2sMXJnDEjM

If you want you can also read this coodeproject article which explains the same in a more visual manner. http://www.codeproject.com/Articles/599756/Five-Great-NET-Framework-4-5-Features#Feature1:-“Async”and“Await”(Codemarkers)

1
static void Main(string[] args)
{
    if (Thread.CurrentThread.Name == null)
        Thread.CurrentThread.Name = "Main";
    Console.WriteLine(Thread.CurrentThread.Name + "1");

    TaskTest();

    Console.WriteLine(Thread.CurrentThread.Name + "2");
    Console.ReadLine();
}


private async static void TaskTest()
{
    Console.WriteLine(Thread.CurrentThread.Name + "3");

    await Task.Delay(2000);
    if (Thread.CurrentThread.Name == null)
        Thread.CurrentThread.Name = "FirstTask";
    Console.WriteLine(Thread.CurrentThread.Name + "4");

    await Task.Delay(2000);
    if (Thread.CurrentThread.Name == null)
        Thread.CurrentThread.Name = "SecondTask";
    Console.WriteLine(Thread.CurrentThread.Name + "5");
}

If you run this program you will see that await will use different thread. Output:

Main1
Main3
Main2
FirstTask4 // 2 seconds delay
SecondTask5 // 4 seconds delay

But if we remove both await keywords, you will learn that async alone doesn't do much. Output:

Main1
Main3
Main4
Main5
Main2
2
  • "await will start new thread": How do you know that the thread is new, and it was just started? It may be a reusable ThreadPool thread, that was started a long time ago. You can query the Thread.IsThreadPoolThread property to find out. Commented Aug 4, 2021 at 1:25
  • 1
    @TheodorZoulias I meant to say "use different thread", but yes, I didn't test if it's new thread or reused thread from pool, so I don't know if it's new or old. I edited my post to "use different thread".
    – milos
    Commented Aug 4, 2021 at 11:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.