await Task, Task.Wait and Friends

I was in one of the threading talks at Build and one of the questions that got raised highlighted that there’s still a lot of confusion around both Task and await in .NET 4.5 which is where you’d be working if you’re working on a Windows Store application.

The best guide for all this is the “Diving Deep with WinRT and await” article but the questions I heard at Build were between;

  • Task.Wait
  • Task.When
  • await Task
  • and then various things about synchronization contexts being captured and/or restored.

Generally, Task.Wait() is Not What You Want

Let’s take a simple example of an asynchronous operation represented by;

Task.Delay(30000);

and play around with it in a Windows Store app. Often in that kind of app, your code is running in response to something happening in the UI so you start off execution on a UI thread but, sometimes, your code may be invoked by something that’s not related to the UI (e.g. a background task) where you wouldn’t be starting off on a UI thread. Let’s deal with the former first though.

Here’s a button click handler;

    async void MyButtonClick(object sender, RoutedEventArgs e)
    {
      // We are on the UI thread.      
      Task task = Task.Delay(30000);

      // This is not a very good idea.
      task.Wait();
    }

Here we ask the framework to give us a Task representing a 30 second delay. Then we call Wait() on that Task.

That’s a blocking wait. What happens with it?

  • A UI thread is running around its loop waiting for something to happen.
  • We click the button in the UI which injects into that loop a piece of work to do.
  • That UI thread picks that work up and invokes the handler.
  • Handler creates a piece of asynchronous work to do which will take 30 seconds.
  • Handler blocks its thread waiting for that work to complete.
  • That UI thread is stuck, blocked, not scheduled by the OS for the next 30 seconds waiting for the work to complete. UI is unresponsive. The app experience is very poor.

That’s a bad idea Smile

Generally, await is What You Want

Here’s another button click handler;

    async void MyButtonClick(object sender, RoutedEventArgs e)
    {
      // We are on the UI thread.      
      Task task = Task.Delay(30000);

      await task;
    }

Here, we’re awaiting the Task. This is very different from what we had in the previous example.

  • A UI thread is running around its loop waiting for something to happen.
  • We click the button in the UI which injects into that loop a piece of work to do.
  • That UI thread picks that work up and invokes the handler.
  • Handler creates a piece of asynchronous work to do which will take 30 seconds.
  • Compiler does work on our behalf to re-structure this method into multiple pieces such that the method logically returns to the caller at line 6 (returning a Task).
  • The UI thread continues its loop, keeping the UI responsive.
  • Compiler does work to ensure that any work that needs to be done once the asynchronous work has completed is scheduled to the UI thread when that asynchronous work has completed (i.e. sort of after the line 6 code above).

To make those last bullet points a bit more obvious it’d be good to have some “additional work” to do after the await call. Something like;

    async void MyButtonClick(object sender, RoutedEventArgs e)
    {
      // When did we start?
      DateTime startTime = DateTime.Now;

      // Where did we start?
      int startThread = Environment.CurrentManagedThreadId;
    
      Task task = Task.Delay(30000);

      await task;

      DateTime endTime = DateTime.Now;
      double elapsed = (endTime - startTime).TotalSeconds;

      // was that about 30 seconds later?
      Debug.Assert((elapsed >= 30) && (elapsed <= 31));

      // are we still on the same thread?
      Debug.Assert(Environment.CurrentManagedThreadId == startThread);
    }

where those assertions check that our code after the await ran around 30 seconds after the code before the await and also that it ran on the UI thread.

await is “SynchronizationContext aware”

await isn’t always about “getting back to the UI thread”, it’s just that in this particular situation it makes sense because we started off on the UI thread so there’s an assumption that we might want to get back to it to continue our work. The await mechanics capture the “SynchronizationContext” for us and restore it when the asynchronous work completes. We can easily change this;

    async void MyButtonClick(object sender, RoutedEventArgs e)
    {
      // When did we start?
      DateTime startTime = DateTime.Now;

      // Where did we start?
      int startThread = Environment.CurrentManagedThreadId;

      // false parameter means "don't capture the context of the original
      // thread and try to restore it".
      await Task.Delay(30000).ConfigureAwait(false);

      DateTime endTime = DateTime.Now;
      double elapsed = (endTime - startTime).TotalSeconds;

      // was that about 30 seconds later?
      Debug.Assert((elapsed >= 30) && (elapsed <= 31));

      // we are no longer on the same thread.
      Debug.Assert(Environment.CurrentManagedThreadId != startThread);
    }

    and now because of the ConfigureAwait() call on line 11 we find that the code after the await call on line 11 is no longer running on that original UI thread that started the work.

    If we have multiple pieces of asynchronous work to do, it might not make sense to keep coming back to the original UI thread (which involves a bit of overhead) after each and every one of those pieces of work. For example, if we have something like;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
          await Task.Delay(10000);
          Debug.Assert(Environment.CurrentManagedThreadId == startThread);
          await Task.Delay(10000);
          Debug.Assert(Environment.CurrentManagedThreadId == startThread);
          await Task.Delay(10000);
          Debug.Assert(Environment.CurrentManagedThreadId == startThread);
        }
    

    then we are still delaying 30 seconds and returning to the UI thread multiple times to kick off the next delay. If there’s no “UI work” to be done at those points (lines 5,7,9) then perhaps it’s better to avoid that overhead of dispatching back on the UI thread;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
          await Task.Delay(10000).ConfigureAwait(false);
          Debug.Assert(Environment.CurrentManagedThreadId != startThread);
          await Task.Delay(10000).ConfigureAwait(false);
          Debug.Assert(Environment.CurrentManagedThreadId != startThread);
          await Task.Delay(10000).ConfigureAwait(false);
          Debug.Assert(Environment.CurrentManagedThreadId != startThread);
        }
    

    Sometimes you might be writing componentry which needs to be aware of whether a caller has particular needs around being called back in a particular SynchronizationContext. If so, you can always grab hold of it in a similar way to await. For instance, here;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          // This is the UI thread.
          int startThread = Environment.CurrentManagedThreadId;
    
          // This is its Sync Context
          SynchronizationContext ctx = SynchronizationContext.Current;
    
          // We do async work passing false to ignore the context.
          await Task.Delay(10000).ConfigureAwait(false);
    
          // We are no longer on the UI thread.
          Debug.Assert(Environment.CurrentManagedThreadId != startThread);
    
          // We might want to get back to it.
          Action callback = () =>
            {
              // We are now on the UI thread again
              Debug.Assert(Environment.CurrentManagedThreadId == startThread);
            };
    
          // This won't happen when invoked from a button in the UI but
          // if this function was elsewhere, it might.
          if (ctx == null)
          {
            callback();
          }
          else
          {
            ctx.Post(
              _ =>
              {
                callback();
              },
              null);
          }
        }
    

    we’re taking explicit steps to tell await (in an artificial way in this example) to not take any notice of the SynchronizationContext and then we’re manually capturing it ourselves and restoring it. Naturally, in this circumstance we’d be better off just letting await deal with it 🙂

    Not Everything Async Needs to be Directly awaited

    In reality, unless there was a dependency amongst these 3 pieces of work, we’d probably want them to happen in parallel rather than in a serial form like we have here so we would not want to just blindly take an approach of “if it’s async, let’s use await on it immediately”. Instead, we might want to be a little more subtle with our approach around using await and let activities that do not have dependencies go off and happen in parallel as below;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
    
          var t1 = Task.Delay(10000);
          var t2 = Task.Delay(10000);
          var t3 = Task.Delay(10000);
    
          await Task.WhenAll(t1, t2, t3);
    
          // Now we do whatever our work is with the UI.
          Debug.Assert(Environment.CurrentManagedThreadId == startThread);
        }
    

    and that’s going to take approximately 10 seconds rather than approximately 30 which is most likely what you’d want in a real example of multiple async pieces of work that don’t have dependencies.

    We can do a similar thing with WinRT. Let’s say that we want to create 2 folders. If there is a dependency then, naturally, we’d need to do them in serial;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
    
          StorageFolder parentFolder =
            await
              ApplicationData.Current.LocalFolder.CreateFolderAsync("parent", CreationCollisionOption.ReplaceExisting).
              AsTask().ConfigureAwait(false);
    
          // NB: As a reader pointed out. At this point, we may be back on the UI thread 
          // or we may not. The ConfigureAwait(false) call is trying to say that we
          // don't care about being on that thread but, I suspect there's logic 
          // inside of CreateFolderAsync which may return a task that is already
          // marked as complete which would presumably mean that there is not
          // a completion callback to invoke on the threadpool.
    
          StorageFolder childFolder =
            await parentFolder.CreateFolderAsync("child", CreationCollisionOption.ReplaceExisting);
    
          // Again, we could be on the UI thread or not...
        } 
    

    whereas if there’s no dependency between them, we can do them in parallel;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
    
          IAsyncOperation<StorageFolder> parentTask =
              ApplicationData.Current.LocalFolder.
              CreateFolderAsync("mother", CreationCollisionOption.ReplaceExisting);
    
          IAsyncOperation<StorageFolder> childTask =
            ApplicationData.Current.LocalFolder.
            CreateFolderAsync("father", CreationCollisionOption.ReplaceExisting);
    
          await Task.WhenAll(parentTask.AsTask(), childTask.AsTask());
    
          // Assume that we are now going to do something with the UI so
          // trying to be back on the UI thread.
          Debug.Assert(startThread == Environment.CurrentManagedThreadId);
        }
    

    because I didn’t use await here I get exposed to the fact that asynchronous operations in WinRT do not return Tasks. They return something descending from IAsyncInfo which surface up in .NET as being awaitable. In this case IAsyncOperation<T>. In order to then wait on two (or more) of those things at the same time I use Task.WhenAll() and call the AsTask() extension to give me something that looks like a Task. A related function would be Task.WhenAny().

    That’s a little bit of where the “facade” slips away around async in WinRT and Task and if you looked at how you do async work across the WinRT boundary then you’d encounter more around those async interfaces and how they work in C++, .NET and JavaScript.

    If we are using await somewhere other than on a UI thread then there’s not necessarily a “UI thread synchronization context” to capture or any other kind of context and so subsequent work does not take account of that context. For example;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int uiThread = Environment.CurrentManagedThreadId;
    
          Task.Run(
            async () =>
            {
              // This is on a threadpool thread.
              int tpThread = Environment.CurrentManagedThreadId;
    
              // That won't be the UI thread.
              Debug.Assert(uiThread != tpThread);
    
              await Task.Delay(10000);
    
              // This won't be the UI thread either.
              Debug.Assert(uiThread != tpThread);
    
              // But it could be pretty much any threadpool thread
              // including the original one (most likely unless
              // other work has come in to make that thread busy).
            }
          );
        }
    

    Here we submit work into the threadpool via Task.Run(). That lambda is an async lambda so can use await. The lambda is going to execute on a threadpool thread and then when the await on line 14 completes the remainder of the function is also going to execute on a threadpool thread but, regardless of the fact that I have not called ConfigureAwait(false) it’s not guaranteed that it will be the same threadpool thread which started the work. The likelihood in this case is that it will be because the original thread has nothing better to do but if that thread was busy then another threadpool thread could be used to complete the work here (i.e. the work that happens on line 17 in this case).

    Awaiting Something That’s Done

    This is an addition to the original post. In that post, I had an example which looked like this;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
    
          await ApplicationData.Current.LocalFolder
            .CreateFolderAsync("foo", CreationCollisionOption.ReplaceExisting)
            .AsTask()
            .ConfigureAwait(false);
    
          // This assertion can fail.
          Debug.Assert(startThread != Environment.CurrentManagedThreadId);
        }
    

    but as a reader pointed out, the assertion on line 11 can fail. That is – even though I have called ConfigureAwait(false) the continuation code on line 11 can still be running on the UI thread.

    How can that be? I suspect it’s because CreateFolderAsync() has the potential to complete its work pretty quickly so may actually be done by the time I get around to calling await on it and there’s really no point going to all the trouble of involving a callback via the threadpool if the result is already available at the time of the call to await. This is different from what I was doing with Task.Delay() which had no chance of completing quickly.

    I think that this can be represented by;

        async void MyButtonClick(object sender, RoutedEventArgs e)
        {
          int startThread = Environment.CurrentManagedThreadId;
    
          // Not asking to be returned to the UI thread but that doesn't mean
          // that we'll necessarily get a threadpool callback...
          Task createFolderTask = ApplicationData.Current.LocalFolder
            .CreateFolderAsync("foo", CreationCollisionOption.ReplaceExisting)
            .AsTask();
    
          if (createFolderTask.IsCompleted)
          {
            // awaiting something that's already completed.
            await createFolderTask.ConfigureAwait(false);
            Debug.Assert(startThread == Environment.CurrentManagedThreadId);
          }
          else
          {
            // there's a race condition here because this task could go
            // from !completed to completed in the blink of an eye.
            await createFolderTask.ConfigureAwait(false);
    
            // This assertion could still (imo) fail because of that
            // race but is perhaps less likely than it was in my 
            // original example.
            Debug.Assert(startThread != Environment.CurrentManagedThreadId);
          }      
        }
    

    and so here we attempt to create a folder and get a Task returned. We then ask that Task whether it has completed or not. Either way, we await it but we assume that if the Task says that it has completed then there’s no real “waiting” to do so we assume (line 15) that our continuation work simply continues on the UI thread.

    If the Task says that it has not yet completed then we await it (line 21) but this time we assume that our continuation work will be scheduled from the threadpool. I suspect that the assertion on line 26 could still fail because we could potentially have a race condition between line 11 and line 21 where the Task could go from “not completed” to “completed” but that could really depend on the internal mechanics of CreateLocalFolder.

    What it points out though is that await is smart enough to not jump through hoops when it doesn’t have to and also that I was being a bit too strong in my original code (and wording) of saying that ConfigureAwait(false) would guarantee that the completion code would not run on the UI thread – what it was really guaranteeing was that the completion code wasn’t fussy about where it ran and so the UI thread was just as good a candidate as any other thread.

    In Summary

    I think there’s a long way to go on Task, async and await and how they link up with the async interfaces in WinRT for Windows 8 or WinPRT for Windows Phone 8.

    The beauty of async/await is that they take away the mechanics of how to write asynchronous code. The beauty of Task is that it gives us an abstraction over threads that takes away the mechanics of how to spin up threads and synchronize with them in order to get work done, get results back, handle exceptions, etc.

    But, even with these new mechanisms, we still have to think hard about what these primitives are doing under the covers and we have to be aware where code is running, what state is shared in order to make smart decisions in a world where tiny changes in syntax can cause huge differences in runtime behaviour.

    For me, that takes some getting used to and I suspect I’m not alone Smile

    (btw – feel free to point out mistakes, those are all mine).