Call to await GetFileAsync() never returns and app hangs in WinRT app

jokeefe picture jokeefe · Jul 3, 2012 · Viewed 13.3k times · Source

I'm attempting to load and read a settings file on application launch, and about 90% of the time, the await GetFileAsync("filename.xml"); never returns, thus, hanging the application.

About a quarter of the time, if I step through the code, it'll actually return and read the file.

Here's a very simplified version of the code:

App.xaml.cs:

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
    FileLoader.Load().Wait();

    // File-load dependent stuff
}

FileLoader.cs:

public async static Task Load()
{
    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file;
    bool fileExists = true;

    try
    {
        // The following line (often) never returns
        file = await folder.GetFileAsync("filename.xml");
    {
    catch
    {
        fileExists = false;
    }

    // Do stuff with loaded file
}

If I watch the Output window in Visual Studio, after awhile of waiting I get "The thread '<No Name>' (0x30c) has exited with code 0 (0x0)."

Does anyone have any idea of what's happening here?

Answer

Stephen Cleary picture Stephen Cleary · Jul 3, 2012

By default, when you await a Task that has not yet completed, the method resumes on a captured context (in this case, the UI context).

So, here's why your code is failing:

  • OnLaunched calls Load (within the UI context).
  • Load awaits. This causes the Load method to return an incomplete task and schedule its completion for later. This continuation is scheduled for the UI context.
  • OnLaunched blocks on the task returned from Load. This blocks the UI thread.
  • GetFileAsync eventually completes, and attempts to run the continuation for Load.
  • The continuation for Load waits for the UI thread to be available so it can execute in the UI context.
  • At this point, OnLaunched is waiting for Load to complete (blocking the UI thread by doing so), and Load is waiting for the UI thread to be free. Deadlock.

These best practices avoid this situation:

  1. In your "library" async methods, use ConfigureAwait(false) whenever possible. In your case, this would change await folder.GetFileAsync("filename.xml"); to await folder.GetFileAsync("filename.xml").ConfigureAwait(false);.
  2. Don't block on Tasks; it's async all the way down. In other words, replace Wait with await.

For more information:

Update, 2012-07-13: Incorporated this answer into a blog post.