39

I have an async method inside a portable class library with this signature:

private async Task<T> _Fetch<T>(Uri uri)

It fetches a resource that is cast back as a concrete type T.

I'm working with a 3rd party cache library (Akavache) that requires a Func<T> as one of the parameters and have tried to do so in this manner:

await this.CacheProvider.GetOrCreateObject<T>(key,
    async () => await _Fetch<T>(uri), cacheExpiry);

This results in the error:

Cannot convert async lambda expression to delegate type 'System.Func<T>'. An async lambda expression may return void, Task or Task<T>, none of which are convertible to 'System.Func<T>'.

I've tried various permutations of Func<T> assignment without any luck, the only way I can get the code to work is to make the Func<T> blocking:

await this.CacheProvider.GetOrCreateObject<T>(key, 
    () => _Fetch<T>(uri).Result, cacheExpiry); 

which deadlocks my app.

Any pointers on where I'm going astray?

Carsten
  • 11,287
  • 7
  • 39
  • 62
Craig Presti - MSFT
  • 1,135
  • 1
  • 12
  • 20

2 Answers2

36

No can do. When someone expects a Func<T> f you can assume it will be invoked with something like result = f() - i.e., it does not know about async behavior. If you cheat it by using .Result like you have, it will deadlock on UI thread because it wants to schedule the code after await (in _Fetch) on the UI thread, but you have already blocked it with .Result.

Async lambda can be passed to Action since it has no return value - or to Func<Task> or Func<Task<T>>.

Looking at your case, the GetOrCreateObject appears to be calling GetOrFetchObject. One of the GetOrFetchObject overloads accepts a Func<Task<T>>. You can try calling that method with your async lambda and see if it helps.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
YK1
  • 7,327
  • 1
  • 21
  • 28
  • 3
    I would like to stress that if a method accepts an `Action` and you pass it an `async` lambda, then that's the wrong thing to do, most of the time. – svick Jun 21 '13 at 11:02
  • @svick: Yes agree with you. Just like method which accepts `Func`, method accepting an `Action` wont know about its async behavior. Async lambda which dont return any result would be ideally passed to `Func`. Its just that C# compiler allows it to be passed to `Action` because there is no return value. – YK1 Jun 21 '13 at 12:08
  • Thanks YK1, I feel quite silly now - I had implemented an interface to abstract away the cache provider and missed the alternate method on the base library. – Craig Presti - MSFT Jun 21 '13 at 17:00
9

YK1's answer explains why you can't treat Func<T> as asynchronous.

To fix your problem, use GetOrFetchObject instead of GetOrCreateObject. The "create" methods assume a (synchronous) creation, while the "fetch" methods work with (asynchronous) retrieval.

await CacheProvider.GetOrFetchObject<T>(key, () => _Fetch<T>(uri), cacheExpiry)

I also removed the unnecessary async/await in your lambda expression. Since _Fetch already returns Task<T>, there's no need to create an async lambda whose sole purpose is to await that task.

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810