Akavache's GetObject hangs when awaited. An

2019-05-07 11:05发布

问题:

I have a Xamarin.Forms application, with this code in my App class (yes, this is just a sample to demonstrate the issue):

    public App()
    {
        BlobCache.ApplicationName = "MyApp";
        BlobCache.EnsureInitialized();


        // The root page of your application
        MainPage = GetMainPage();
    }

    public object BlockingGetExternalUser()
    {
        return GetExternalUser().Result;
    }

    private async Task<object> GetExternalUser()
    {
        try
        {
            return await BlobCache.LocalMachine.GetObject<object>("user");
        }
        catch (KeyNotFoundException)
        {
            return null;
        }
    }

The Key "user" does not exist, so I would expect to get a KeyNotFoundException. However I never see this exception being thrown. Instead it just "hangs" and never returns from the await GetObject call.

I am running this on my phone with Android 5.0.

Any ideas how to fix this? Am I doing something fundamentally wrong?

Update: On a side note: Instead of immediately trying GetObject, one could try to check if the key actually exists in the cache and only then retrieve it from the cache. However, if I am not mistaken, there is no other way to do a check other than calling GetObject and catching the exception like in the sample above. For a scenario where one would just want to know if an item exists, that doesn't seem to be ideal. Maybe an "Exists()" method would be a nice to have in Akavache? Or maybe I am missing something?

Update2: Changing the example to not use an async method in the constructor. Just to prove a point that that is not the issue.

Update3: Removing the call from the constructor. When I call BlockingGetExternalUser from anywhere in my code, the await will still hang.

回答1:

You're most certainly having a dead-lock. Quoting Synchronously waiting for an async operation, and why does Wait() freeze the program here:

The await inside your asynchronous method is trying to come back to the UI thread.

Since the UI thread is busy waiting for the entire task to complete, you have a > deadlock.

Note that your call to .Result implies a Task.Wait() somewhere.

There are two solutions: Either completely avoid the async methods, or wrap your code into a Task.Run like this:

public object BlockingGetExternalUser()
{
    return Task.Run<object>(() => GetExternalUser().Result);
}

(I hope it's compiling I didn't verify in VS :)

By experience I tend to avoid async methods in combination with SQLite these days. Reason is that most SQLite wrapper libraries use the Task.Run anti-pattern to provide async wrappers around their methods while SQLite doesn't have any intrinsic notations of being asynchronous. Note though that it's perfectly fine for you to wrap things into Task.Run to make them asynchronous and that it's only an anti-pattern for library designers, suggesting to their users that methods are asynchronous when they're actually not. You can read more about this here: Task.Run as an anti-pattern?



回答2:

Using async methods in a constructors (var externalUser = GetExternalUser().Result;) is considered as a bad code. You shouldn't use async methods in a class constructors. Read this: Can constructors be async?

You could try to change it to avoid deadlocks:

Func<Task> task = async () => { await GetExternalUser().ConfigureAwait(false); };
task().Wait();

... but I won't recommend it.