Sunday, March 01, 2015

Async Await By Example - Part 1

When I first started using C#’s async/await keywords I found it helpful to create a set of notes as I progressed.  On this post you’ll find a collection of tips and how-tos.  Hopefully others will find it useful.

Quick Tips

* Each managed thread in .NET reserves around 1MB of virtual memory
* Only a method marked async can contain the await keywords

Using NUnit to Run Test Methods

I’m using NUnit as a convenience to step through my test code.  As of v2.6.2, NUnit supports unit tests with the async keyword. 


 Beginning with NUnit 2.6.2, test methods targetting .Net 4.5 may be marked as async and NUnit will wait for the method to complete before recording the result and moving on to the next test.Async test methods may return void or Task if no value is returned, or Task<T> if a value of type T is returned.


To support the test code here, I’ve added a general logger class that extracts the calling method and thread id:
public static class Logger
{
    public static void Log(string message, [CallerMemberName]string caller = null)
    {
        LogInternal(message, caller);
    }
 
    private static void LogInternal(string message, string caller)
    {
        Console.WriteLine("{0:HH:mm:ss.ff} ({1:00}) [{2}]: {3}", 
            DateTime.Now,
            Thread.CurrentThread.ManagedThreadId, 
            caller, message);
    }
}

The following test has Timeout attribute assigned so it will fail as the task is not returned with 1000 milliseconds):

[Test]
[Timeout(1000)]
public async Task AsyncLambdaTimeout()
{
    Logger.Log("Start");
 
    Func<Task> slowTask = async () =>
    {
        Logger.Log("Inside..");
        await Task.Run(() =>
        {
            Logger.Log("Sleeping1...");
            Task.Delay(1500).Wait();
            Logger.Log("...Awake1");
        });
    };
 
    Logger.Log("Before await");
    await slowTask();
 
    Logger.Log("End");
}

Rest results: 
18:03:31.21 (09) [AsyncLambdaTimeout]: Start
18:03:31.23 (09) [AsyncLambdaTimeout]: Before await
18:03:31.24 (09) [AsyncLambdaTimeout]: Inside..
18:03:31.25 (04) [AsyncLambdaTimeout]: Sleeping1...
Test exceeded Timeout value of 1000ms 

Notice how Logger.Log("...Awake1") never gets called as we waited 1500 milliseconds prior.

Return Statement 

The return value from async method can be void, Task or Task<T>. 
void is rarely used unless it’s truly a fire and forget operation (it's best to return async Task as easier for caller to use await to wait and makes exception handling easier) 
For void and Task the return statement is optional or must be just “return;” 
Task<T> must return a type T (the compiler will wrap this in a Task<T> for you)

Where async Cannot be Used


1. Catch (although that’s now changed in C#6)

[Test]
public async Task<string> InvalidInCatch()
{
    var result = string.Empty;
 
    try
    {
        result = await SlowRunningMethodWithError();
    }
    catch (Exception)
    {
        // Give compiler error: Cannot await in the body of a catch clause
        result = await SlowRunningMethod();
    }
 
    return result;
}

2. Lock

There’s no point in locking on an object as the calling thread will release the synclock but get called back on a different thread. 

private readonly object _sync = new object();
[Test]
public async Task<string> InvalidInLock()
{
    var result = string.Empty;
 
    var sync = new object();
    lock (_sync)
    {
        // Gives compiler error:
        // The 'await' operator cannot be used in the body of a lock statement
        result = await SlowRunningMethod();
    }
 
    return result;
}

3. Most parts of linq


4. Unsafe code


Nothing to Return From an Async Call?


If you’ve got an async call that does return anything, ie some sort of fire and forget operation, it’s best to use async Task

Exceptions

Running this await call, the exception will be caught:
[Test]
public async Task<string> AwaitCatchException()
{
    var result = "";
    try
    {
        result = await SlowRunningMethodWithError();
    }
    catch (Exception ex)
    {
        // Will catch the exception
        Logger.Log(ex.ToString());
    }
 
    return result;
}
private static Task<string> SlowRunningMethodWithError()
{
    Logger.Log("SLOWIE: START");
 
    var delay = Task.Run(() =>
    {
        Task.Delay(2000).Wait();
        throw new ApplicationException("oops");
        return DateTime.Now.ToString("HH:mm:ss.ff");
    });
 
    Logger.Log("SLOWIE: END");
    return delay;
}
Results:
The MoveNext method name – line 17 in my case does refer to this line:
result = await SlowRunningMethodWithError();

18:42:26.50 (05) [SlowRunningMethodWithError]: SLOWIE: START
18:42:26.52 (05) [SlowRunningMethodWithError]: SLOWIE: END
18:42:28.55 (04) [CatchExceptions]: System.ApplicationException: oops
at AsyncAwaitDemo.AABasics.ExceptionTests.<SlowRunningMethodWithError>b__4() in ExceptionTests.cs:line 34
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at ExceptionTests.<CatchExceptions>d__0.MoveNext() in ExceptionTests.cs:line 17

Conversely, calling SlowRunningMethodWithError as a task, the exception will only be caught when you await the task:
[Test]
public async Task<string> NeedsAwaitToGetException()
{
    var result = "";
 
    // Note exception will fire when we call await - not in the next line
    // this behaviour is a change to .NET!!!!
    var task = SlowRunningMethodWithError();
 
    try
    {
        // exception will be trigged here
        result = await task;
    }
    catch (ApplicationException ex)
    {
        // Will catch the exception
        Logger.Log(ex.ToString());
    }
 
    return result;
}
Output:
18:58:21.49 (05) [SlowRunningMethodWithError]: SLOWIE: START
18:58:21.52 (05) [SlowRunningMethodWithError]: SLOWIE: END
18:58:23.55 (04) [NeedsAwaitToGetException]: System.ApplicationException: oops
at ExceptionTests.cs:line 59
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at ExceptionTests.cs:line 40

Recommendations on When to Await


The synchronous parts in an async method, ie up to making an async all should be minimal, e.g. checking arguments, looking in a cache to avoid the async call.

Is it Running Async?


Just because you’re using an async method doesn’t mean it’s going to get called in an async manner.  Methods start synchronously until an async method calls await
Given this slow running method that returns a Task<string>

private static Task<string> SlowRunningMethod()
{
    Logger.Log("SLOWIE: START");
 
    var delay = Task.Run(() =>
    {
        Task.Delay(2000).Wait();
        Logger.Log("After delay");
        return DateTime.Now.ToString("HH:mm:ss.ff");
    });
 
    Logger.Log("SLOWIE: END");
    return delay;
}



Running these two tests:

[Test]
public async Task<string> AwaitDirect()
{
    Logger.Log("Start");
 
    // Following line is ran async - will have diffrent thread id
    var retValue = await SlowRunningMethod();
    Logger.Log(string.Format("End. RetValue={0}", retValue));
 
    return retValue;
}

[Test]
public async Task<string> DefineAsTaskAndAwait()
{
    Logger.Log("Start");
 
    // The method will start on the current thread - so will block it
    // it will return a Task<string> in the current thread
    var slowTask = SlowRunningMethod();
 
    Logger.Log("In the middle");
 
    var retValue = await slowTask;
    Logger.Log(string.Format("End. RetValue={0}", retValue));
 
    return retValue;
}
Give this result:

AwaitDirect
DefineAsTaskAndAwait
(05) [AwaitDirect]: Start
(05) [SlowRunningMethod]: SLOWIE: START
(05) [SlowRunningMethod]: SLOWIE: END

(04) [SlowRunningMethod]: After delay
(04) [AwaitDirect]: End. RetValue=18:06:28.26
(05) [DefineAsTaskAndAwait]: Start
(05) [SlowRunningMethod]: SLOWIE: START
(05) [SlowRunningMethod]: SLOWIE: END
(05) [DefineAsTaskAndAwait]: In the middle
(04) [SlowRunningMethod]: After delay
(04) [DefineAsTaskAndAwait]: End. RetValue=18:08:43.89




State Machine

Prior to calling to awaitable code, the compiler will generate a state machine containing a copy of all local variables:

  • Parameters to the method
  • Variables in scope
  • Other variables, eg loop counters
  • this is the method if non-static


More to follow

No comments: