Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 480
jokeefe Avatar asked Jul 03 '12 17:07

jokeefe


1 Answers

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:

  • Await, and UI, and deadlocks! Oh, my!
  • My async/await intro post, which includes a brief description of how Task awaiters use SynchronizationContext and introduces some best practices.
  • The Async/Await FAQ, which goes into more detail on the contexts.
  • This MSDN forum post.
  • Stephen Toub demos this deadlock, and so does Lucian Wischik.

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

like image 53
Stephen Cleary Avatar answered Oct 31 '22 18:10

Stephen Cleary