Blog Archives

Async and await (advanced, .Net 4.5, C# 5)

The importance is in the details. It all looks easy, but follow each step carefully today.

Windows pauses threads that are waiting for I/O operations to complete (eg. internet or file access). The same threads cannot be used for other jobs in the meantime and new threads need to be created. You could use tasks to solve this specific problem. The program would start an asynchronous task to deal with an I/O operation. After a while the same task would trigger a follow-up procedure via continuation task. It requires some work to cover all code paths, but it can be done.

C# 5 has implemented new keywords to make your life easier. You can use async to mark methods for asynchronous operations, which start synchronously and then split up as soon as the program arrives at any await keyword.

The below Print() method prints the time, sequence and ThreadId. This information is useful to understand the program cycle.

private static void Print(int xStep) {
    Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " step " + xStep + " , thread " + Thread.CurrentThread.ManagedThreadId);
} //

static async void AsyncCalls1() {
    Print(1);
    int i = await Task.Run<int>(() => {
        Print(2); Thread.Sleep(5000);
        Print(3); return 0;
    });
    Print(4);  // same thread as in step 3

    Console.ReadLine();
    // return void
} //

example output:
19:09:36 step 1 , thread 9
19:09:36 step 2 , thread 10
19:09:41 step 3 , thread 10
19:09:41 step 4 , thread 10

The above code is a warm up for us. The method AsyncCalls1() returns void. I emphasize this seemingly insignificant fact here. If you do not return void then the compiler will complain. It wants you to add async in the calling method as well. But if you do so, then it would also ask you to add async in the calling method, that called the calling method. It would be an endless game until you arrive at Main(). And there you would not know what to do, because you cannot use async in Main(). Novices can get quite frustrated with such minuscule glitch.

What is the program doing? It starts new task, which uses another thread from the thread pool. The original thread is then neglected, there is no follow-up. Now check this out: When the created task ends, the program continues with (Task.ContinueWith()) the same thread, which it was using in the task. It seems there is no context switching.

static async void AsyncCalls2() {
    Print(1); Task<int> task = AsyncCalls3();
    Print(4); int x = await task;
    Print(7); // same thread as in step 6

    Console.ReadLine();
    // return void
} //

static async Task<int> AsyncCalls3() {
    Print(2);
    int i = await Task.Run<int>(() => {
        Print(3); Thread.Sleep(5000);
        Print(5); return 0;
    });
    Print(6); return i;  // same thread as in step 5, returning an INTEGER !!!
} //

example output:
19:10:16 step 1 , thread 9
19:10:16 step 2 , thread 9
19:10:16 step 3 , thread 10
19:10:16 step 4 , thread 9
19:10:21 step 5 , thread 10
19:10:21 step 6 , thread 10
19:10:21 step 7 , thread 10

Method AsyncCalls3() has a return value, which is a Task. The task that is started inside this method returns an integer. But doesn’t Task.Run() have to return Task<int> according to its definition? It is the await that changes the behavior. It returns the integer value (0). await has been implemented to shorten code, and this is what it does. The code is more legible.

Method AsyncCalls2() calls AsyncCalls3()
and receives an integer and not a Task<int>. This is caused by the async keyword.
AsyncCalls2() itself returns void. This is the same issue as with AsyncCalls1(). However AsyncCalls3() can return a value to AsyncCalls2(), because AsyncCalls2() itself uses the async keyword in the method definition.

Check the program sequence. I marked the steps clearly to make comprehension easy. And then analyse the thread context switching. Between step 2 and 3 is a context switch operation, but not between 5, 6 and 7. This is the same behavior as in the first example code.

public static async void AsyncCalls4() {
    Print(1); string s = await AsyncCalls5();
    Print(4);

    Console.ReadLine();
    // return void
} //

// using System.Net.Http;
public static async Task<string> AsyncCalls5() {
    using (HttpClient lClient = new HttpClient()) {
        Print(2); string lResult = await lClient.GetStringAsync("http://www.microsoft.com");
        Print(3); return lResult; 
    }
} //

example output:
19:11:47 step 1 , thread 10
19:11:47 step 2 , thread 10
19:11:48 step 3 , thread 14
19:11:48 step 4 , thread 14

When searching for async and await on the web you will find the emphasis on I/O. Most example programs concentrate on this and don’t explain what is roughly going on inside the async-I/O method itself. Basically .Net async-I/O methods deal with tasks and use a similar construction to Task.ContinueWith(). This is why I concentrated on different examples that can be used in any context (even though not very meaningful examples). The internet download example is more or less a classical one. You can use await on many I/O methods. Keep in mind that AsyncCalls4() returns void and that you are not supposed to call AsyncCalls5() from the Main() method, because you would have to add async to it.

Advertisements

Exiting Threads

You can use the Thread.Abort() method to kill a thread. This throws a ThreadAbort-Exception. The problem with this is that you do not know where exactly your program stops executing. It could be in the middle of an important calculation that makes a proper resource cleanup impossible. A more proper way would include a shared variable. By testing that variable you can exit your thread at predefined code positions.

private class parameters {
  //public double a;
  //public string s;
  public bool exitFlag = false;
}//

static private void myThread(object xParameters) {
  parameters p = xParameters as parameters;
  int i = 0;
  while (!p.exitFlag) {
    Console.WriteLine("thread loop number " + i++);
    Thread.Sleep(1000);
  }
  Console.WriteLine("good bye!");
  Thread.Sleep(2000);
} //

static void Main(string[] args) {
  Thread t = new Thread(new ParameterizedThreadStart(myThread));
  t.IsBackground = true;
  t.Name = "MyBackgroundThread";
  t.Priority = ThreadPriority.Normal;
  parameters p = new parameters();
  t.Start(p);

  Console.WriteLine("press return to exit the program");
  Console.ReadLine();

  p.exitFlag = true;
  t.Join();
} //

You can use a global static boolean to shut down a thread, but I think you will appreciate my solution using parameters as you can then reuse standard parameter interfaces and make your code more generic. You surely don’t want to deal with global variables when you have 100 threads running.

Exiting your application

In Windows.Forms applications you can use Application.Exit().  This method  terminates all message loops and closes all windows. You can eg. execute your cleanup code in the Form.OnClose events.
This method does not terminate your threads. If you do not deal with all your Foreground threads properly, then these threads  keep running. Therefore you must take measures to kill your threads.

The Environment.Exit() method is not part of Windows.Forms. It kills the process. Unsaved changes in forms may get lost. Again, you have to clean up properly and free resources.

 

Basic Thread

static private void myThread() {
  Thread.Sleep(2000);
  Console.WriteLine("hello world!");
} //

static void Main(string[] args) {
  Thread t = new Thread(new ThreadStart(myThread));
  t.IsBackground = true;
  t.Name = "MyBackgroundThread";
  t.Priority = ThreadPriority.Normal;
  Console.WriteLine("starting new thread");
  t.Start();

  Console.WriteLine("waiting for started thread to finish");
  t.Join();
  Console.WriteLine("press return to exit the program");
  Console.ReadLine();
} //

Nearly all PCs these days have multiple cores and/or CPUs. There is a speed benefit using them simultaneously. Windows can run applications independently. You can eg. run Notpad and Paint in parallel. Each application runs as a process. And each process can have many threads. A thread is more or less some code that is executed for a certain time period. Windows decides on its own when to pause a thread and switch to another thread. This is called context switching.

To run a simple thread we need the “System.Threading” namespace. You declare it with “using System.Threading;”.

The output console is a static and thread safe class. You neither have to instantiate the class nor synchronise the output. This is quite helpful, otherwise the example program would have to be a bit more complex. You would not be able to get access to the console from two different threads without risking deadlocks.

It is good practice to define the thread priority. That way Windows knows how important your code is and how much time it should assign for its execution. In a stock trading system you would eg. assign a high priority to the trade decision thread and a low priority to any screen output.

The join method is called on the main thread to let it wait until our newly generated thread terminates. You cannot restart a thread once it has terminated. In our example we started a background thread by setting t.IsBackground to true. All background threads (and the application) automatically stop as soon as all Foreground threads have terminated. Thus Foreground threads need to handle the shut down process, otherwise the application keeps on running unintentionally.

Some threads need parameters. Have a look at the following code. It uses a class to pass two parameters to the new thread.

private class parameters {
  public  double d;
  public  string s;
}//

static private void myThread(object xParameters) {
  parameters p = xParameters as parameters;
  Console.WriteLine("your parameters are " + p.d + " " + p.s);
} //

static void Main(string[] args) {
  Thread t = new Thread(new ParameterizedThreadStart(myThread));
  t.IsBackground = true;
  t.Name = "MyBackgroundThread";
  t.Priority = ThreadPriority.Normal;
  parameters p = new parameters{d = 2.5, s = "meters"};
  t.Start(p);
  t.Join();
  Console.WriteLine("press return to exit the program");
  Console.ReadLine();
} //