Преобразовать асинхронное лямбда-выражение в тип делегата System.Func‹T›?

У меня есть асинхронный метод внутри переносимой библиотеки классов с этой подписью:

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

Он извлекает ресурс, который возвращается как конкретный тип T.

Я работаю со сторонней библиотекой кеша (Akavache), которая требует Func<T> в качестве одного из параметров и пытался сделать это таким образом:

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

Это приводит к ошибке:

Не удается преобразовать асинхронное лямбда-выражение в тип делегата "System.Func<T>". Асинхронное лямбда-выражение может возвращать void, Task или Task<T>, ни одно из которых не может быть преобразовано в 'System.Func<T>'.

Я безуспешно пробовал различные перестановки присваивания Func<T>, единственный способ заставить код работать - это блокировать Func<T>:

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

который блокирует мое приложение.

Любые указатели на то, где я сбиваюсь с пути?


person Craig Presti - MSFT    schedule 21.06.2013    source источник
comment
Вы ищете Func<T, Task>   -  person Jerry Nixon    schedule 08.03.2016


Ответы (3)


Нет не могу. Когда кто-то ожидает Func<T> f, вы можете предположить, что он будет вызываться с чем-то вроде result = f(), то есть он не знает об асинхронном поведении. Если вы обманете его, используя .Result, как у вас, - он заблокируется в потоке пользовательского интерфейса, потому что он хочет запланировать код после await (в _Fetch) в потоке пользовательского интерфейса, но вы уже заблокировали его с помощью .Result.

Асинхронная лямбда может быть передана в Action, поскольку она не имеет возвращаемого значения, или в Func<Task> или Func<Task<T>>.

Глядя на ваш случай, кажется, что GetOrCreateObject звонит GetOrFetchObject. Одна из перегрузок GetOrFetchObject принимает файл Func<Task<T>>. Вы можете попробовать вызвать этот метод с помощью асинхронной лямбды и посмотреть, поможет ли это.

person YK1    schedule 21.06.2013
comment
Я хотел бы подчеркнуть, что если метод принимает Action, а вы передаете ему async лямбду, то в большинстве случаев это неправильно. - person svick; 21.06.2013
comment
@svick: Да, согласен с тобой. Как и метод, который принимает Func<T>, метод, принимающий Action, не будет знать о своем асинхронном поведении. Асинхронная лямбда, которая не возвращает никакого результата, в идеале должна быть передана в Func<Task>. Просто компилятор С# позволяет передать его в Action, потому что нет возвращаемого значения. - person YK1; 21.06.2013
comment
Спасибо, YK1, теперь я чувствую себя довольно глупо - я реализовал интерфейс для абстрагирования поставщика кеша и пропустил альтернативный метод в базовой библиотеке. - person Craig Presti - MSFT; 21.06.2013

ответ YK1 объясняет, почему вы не можете рассматривать Func<T> как асинхронный.

Чтобы решить проблему, используйте GetOrFetchObject вместо GetOrCreateObject. Методы «создания» предполагают (синхронное) создание, тогда как методы «выборки» работают с (асинхронным) извлечением.

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

Я также удалил ненужные async/await в вашем лямбда-выражении. Поскольку _Fetch уже возвращает Task<T>, нет необходимости создавать async лямбду, единственной целью которой является await выполнение этой задачи.

person Stephen Cleary    schedule 21.06.2013
comment
Спасибо, Стивен, именно так я решил проблему. - person Craig Presti - MSFT; 21.06.2013

Что-то вроде этого?

 Public Func<T> ConvertTask<T>(Task<T> task)
 {
     return ()=>task.Result;
 }
person Tormod    schedule 21.06.2013
comment
В вопросе ясно сказано, что использование Result вызывает взаимоблокировку. - person svick; 21.06.2013