4

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.

theB
  • 6,450
  • 1
  • 28
  • 38
larsbeck
  • 665
  • 2
  • 7
  • 11
  • There are a few different ways to handle this depending on what you want to do with the object result. How do you plan on using user object? – Andres Castro Jul 15 '15 at 21:36
  • First of all, let me stress that the call does not happen in the constructor in my code. I probably should not have made a sample that makes people think this could be the problem. Secondly, to answer your question, I am using this in many places, for example in an HttpClientHandler, which takes ExternalUser and puts a bearer token into the request. – larsbeck Jul 16 '15 at 05:48

2 Answers2

6

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?

Community
  • 1
  • 1
dmunch
  • 384
  • 2
  • 5
  • I needed to change a little the code: return Task.Run(() => GetExternalUser().Result).Result; But it worked. – Albert Cortada Sep 01 '16 at 07:46
  • almost all of the cache methods return an IObservable. You can convert these to Tasks and then call configureAwait(false) which then gives you some other options that won't cause the deadlock. For example, await BlobCache.LocalMachine.getObject("user").FirstAsync().ToTask().ConfigureAwait(false); – chad Oct 03 '16 at 20:26
1

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.

Community
  • 1
  • 1
Daniel Luberda
  • 7,374
  • 1
  • 32
  • 40
  • I know. It is not like that in my code. I made a minimal example to enable others to repro the issue. Maybe even for a minimal example that was a bad idea. Anyway, I appreciate the hint. – larsbeck Jul 15 '15 at 16:13
  • Just for the fun of it I changed the example so that there is no async call (although I believe that .Result would actually be fine) in the constructor. The issue persists though. – larsbeck Jul 15 '15 at 16:49
  • But it's still an async call inside constructor after the changes. Change it to the code I used and see if it works. – Daniel Luberda Jul 15 '15 at 16:53
  • I removed the call from the constructor to prove the point that the issue has nothing to do with the constructor. When I call the public method from anywhere in my code, the await still hangs. – larsbeck Jul 16 '15 at 05:46