Я вызываю асинхронную функцию из вычислительного выражения task {...}
. Обычно я могу использовать let!
для вызова асинхронных функций и ожидания их результатов. Однако, если я перенесу вызов функции в выражение try/with
, я получу следующую ошибку компилятора.
This construct may only be used within computation expressions
Поскольку этот код все еще находится в выражении task{...}
, я не уверен, что именно имеет в виду компилятор.
В следующем надуманном примере вызов getDataMayThrowAsync
из controller1
компилируется, но в controller2
я получаю указанную выше ошибку компиляции.
module LetBangIssue =
open System
open System.Threading.Tasks
let getDataMayThrowAsync (id:int) = task {
do! Task.Delay(10)
let random = Random()
if random.Next(1, 1000) < 500 then
raise <| Exception "Something went wrong"
return "the data"
}
// Compiles, but doesn't handle error case...
let controller1() = task {
let! tryGetTheData = getDataMayThrowAsync 10
return tryGetTheData
}
// Attempt to handle error case, but can't compile...
let controller2 () = task {
let theData =
try
let! tryGetTheData = getDataMayThrowAsync 10
tryGetTheData
with
| ex ->
printfn $"Error: {ex.Message}"
"couldn't get the data"
return theData
}
Как я могу исправить controller2
, чтобы решить ошибку компиляции?
Проблема не в try
/with
. Это дополнительный let
. Попробуй это:
let controller2 () = task {
let theData =
let! tryGetTheData = getDataMayThrowAsync 10
tryGetTheData
return theData
}
Это не работает с той же ошибкой, потому что в теле theData
вы используете let!
без окружения task { }
.
Как написано, дополнительный let
вообще не нужен, так как вы просто возвращаете его в конце. Так что это будет работать нормально:
let controller2 () = task {
try
let! tryGetTheData = getDataMayThrowAsync 10
return tryGetTheData
with
| ex ->
printfn $"Error: {ex.Message}"
return "couldn't get the data"
}
Но если вам действительно нужен дополнительный let
, вы должны сделать его let!
(потому что он привязан к асинхронному результату), вы должны обернуть его тело в task
(иначе вы не сможете использовать let!
внутри него), и вы должны используйте return
, чтобы вернуть значения из него:
let controller2 () = task {
let! theData = task {
try
let! tryGetTheData = getDataMayThrowAsync 10
return tryGetTheData
with
| ex ->
printfn $"Error: {ex.Message}"
return "couldn't get the data"
}
return theData
}
Ваша попытка сделала все это синхронным. Вместо того, чтобы перехватывать результаты вычислений через продолжения (также известные как «обратные вызовы»), ваше решение остановит поток и дождется завершения асинхронной задачи, а затем продолжит.
О да. Я вижу это сейчас. Похоже, что Async.AwaitTask |> Async.RunSynchronously
похож на GetAwaiter().GetResult()
в C#.
Да, точно. За исключением того, что я думаю, что это немного более явно о том, что происходит из-за того, что в названии есть «Синхронно».
Отлично! Можно вопрос вдогонку? Одной из моих попыток исправить ошибку компиляции была
let tryGetTheData = getDataMayThrowAsync 10 |> Async.AwaitTask |> Async.RunSynchronously
. Казалось, что это сработало, но не похоже на правильный подход. Я не уверен, почему. Являются ли асинхронные функции неуместными в таком случае?