FileIO.ReadTextAsync occasionally hangs

2019-02-25 14:35发布

问题:

I'm just experimenting with WinRT and one demo app i'm creating is a basic "notepad" style app which loads/saves to local storage. Whilst I'm familiar with the proper async approach for building WinRT apps, my demo app is using a synchronous Load to keep things simple.

The problem is that when a call is made to Load, it works 2 out of 3 times and the rest of the time the app hangs on the call var result = await FileIO.ReadTextAsync(storageFile);

public class ContentStorage : IContentStorage
{
    private const string FileName = "contents.txt";

    public string Load()
    {
        return LoadAsync().Result;
    }

    public void Save(string content)
    {
        SaveAsync(content);
    }

    private static async Task<string> LoadAsync()
    {
        var storageFile = await LocalFolder.GetFileAsync(FileName);
        var result = await FileIO.ReadTextAsync(storageFile);

        return result;
    }

    private static async void SaveAsync(string content)
    {
        var storageFile = await LocalFolder.CreateFileAsync(FileName, CreationCollisionOption.ReplaceExisting);

        FileIO.WriteTextAsync(storageFile, content);
    }

    private static StorageFolder LocalFolder
    {
        get { return ApplicationData.Current.LocalFolder; }
    }
}

Am I doing something extraordinarily stupid here?

FWIW, I experimented with changing Load to just explicitly block on each step and this improves the hanging to 1 in 20, but I still don't understand why it's hanging at all...

public string Load()
{
    var storageFile = LocalFolder.GetFileAsync(FileName).AsTask().Result;
    var result = FileIO.ReadTextAsync(storageFile).AsTask().Result;

    return result;
}

回答1:

Whilst I'm familiar with the proper async approach for building WinRT apps, my demo app is using a synchronous Load to keep things simple.

Not really. Mixing synchronous with asynchronous code is extremely complex. It's far simpler to just use async everywhere.

When an async method continues execution after waiting for a task, it will return to its original context by default. (I cover this in more detail in my async/await blog post). Some contexts (such as UI contexts) only permit a single thread; if that thread is blocked (e.g., on Task.Result), then the async method cannot enter that context to complete its execution. This causes a deadlock.

For more information:

  • The async/await FAQ has a lot of detail on the context capture and resume.
  • Stephen Toub on the Parallel Team Blog has another blog post Await, and UI, and deadlocks! Oh, my!, which explains this particular deadlock situation in detail.
  • I wrote an exhaustive answer for this kind of deadlock in an MSDN forum post.

This deadlock is famous enough that it's actually been demo'd by Microsoft:

  • By Stephen Toub at the BUILD conference (Sept 16, 2011).
  • By Lucian Wischik at DevConnections (Mar 26, 2012).


回答2:

Try to use ConfigureAwait(false) with the await operation, may be ReadTextAsync is not thread safe, so it will hang the UI thread when the await finished and back to the UI thread.