Ответ 1
Разница между вашим первым и вторым фрагментами заключается в том, что вызов AwaitTask
происходит в разных потоках.
Попробуйте это, чтобы проверить:
let printThread() = printfn "%d" System.Threading.Thread.CurrentThread.ManagedThreadId
let result =
printThread()
func()
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
let res2 =
printThread()
async {
printThread()
let! temp = func() |> Async.AwaitTask
return temp
} |> Async.RunSynchronously |> Array.ofSeq
Когда вы запустите res2
, вы получите две строки вывода с двумя разными номерами. Нить, на которой работает async
, - это не тот поток, на котором выполняется сама res2
. Погружение в async
помещает вас в другой поток.
Теперь это взаимодействует с тем, как работают задачи .NET TPL. Когда вы ходите на задание, вы не просто получаете обратный вызов в какой-то случайной нити, о нет! Вместо этого ваш обратный вызов назначается через "текущий" SynchronizationContext
. Это особый вид зверя, из которого всегда есть один "текущий" (доступный через статическое свойство - говорить о глобальном состоянии!), И вы можете попросить его запланировать материал "в том же контексте", где концепция "тот же контекст" определяется реализацией.
WinForms, конечно, имеет свою собственную реализацию, точно названную WindowsFormsSynchronizationContext
. Когда вы работаете в обработчике событий WinForms и запрашиваете текущий контекст для планирования чего-то, он будет запланирован с использованием собственного цикла событий WinForms - a la Control.Invoke
.
Но, конечно, поскольку вы блокируете поток цикла событий с помощью Async.RunSynchronously
, задача не будет иметь шансов на успех. Вы ждёте, что это произойдет, и он ждет вас, чтобы выпустить поток. Ака "тупик".
Чтобы исправить это, вам нужно запустить ожидание в другом потоке, чтобы контекст синхронизации WinForms не использовался - решение, на которое вы случайно наткнулись.
Альтернативным рекомендуемым решением является указание TPL явно не использовать "текущий" контекст через Task.ConfigureAwait
:
let result =
func().ConfigureAwait( continueOnCapturedContext = false )
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
К сожалению, это не скомпилируется, потому что Async.AwaitTask
ожидает Task
, а ConfigureAwait
возвращает ConfiguredTaskAwaitable
.