NB: I am not a professional software developer but I do write a lot of code that doesn't make use of asynchronous anything, so I apologize if this question is really straight forward.
I am interfacing with a library written in C#. There is a particular function (lets call it 'func') that returns a 'Threading.Tasks.Task>'
I am building a library in F# that makes use of func. I tested the following piece of code in a console app and it worked fine.
let result =
func()
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
However, when I run it from a WinForms app (which is ultimately what I want to do), execution blocks in the form code and never returns.
So I messed around with the code and tried the following, which worked.
let result =
async{
let! temp =
func()
|> Async.AwaitTask
return temp
} |> Async.RunSynchronously |> Array.ofSeq
Why didn't the first snippet work? Why did the second snippet work? Is there anything on this page that would answer either of these questions? If so, it does not seem obvious. If not, can you point me to somewhere that does?
The F-Pattern describes the most common user eye-scanning patterns when it comes to blocks of content. 'F' means fast. That's how users read your content on the web. In a few seconds, users eyes move at amazing speeds across a' page.
Bearing the F-shaped pattern in mind will help you create a design with good visual hierarchy—a design that people can scan easily. The F-shaped layout feels comfortable for most western readers, because they have been used to reading from top to bottom / left to right, for their entire lives.
They don't. People rarely read Web pages word by word; instead, they scan the page, picking out individual words and sentences. In a recent study John Morkes and I found that 79 percent of our test users always scanned any new page they came across; only 16 percent read word-by-word.
Web users spend 69% of their time viewing the left half of a page and 30% of their time viewing the right half.
The difference between your first and second snippets is that the AwaitTask
call happens on different threads.
Try this out to verify:
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
When you run res2
, you'll get two lines of output, with two different numbers on them. The thread on which the inside of async
runs is not the same thread on which res2
itself runs. Diving into an async
puts you on a different thread.
Now, this interacts with the way .NET TPL tasks actually work. When you go to await a task, you don't just get a callback on some random thread, oh no! Instead, your callback gets scheduled via the "current" SynchronizationContext
. That's a special kind of beast, of which there is always one "current" one (accessible via a static property - talk about global state!), and you can ask it to schedule stuff "in the same context", where the concept of "same context" is defined by the implementation.
WinForms, of course, has its own implementation, aptly named WindowsFormsSynchronizationContext
. When you're running within a WinForms event handler, and you ask the current context to schedule something, it will be scheduled using the WinForms' own event loop - a la Control.Invoke
.
But of course, since you're blocking the event loop's thread with your Async.RunSynchronously
, the task await never has a chance to happen. You're waiting for it to happen, and it is waiting for you to release the thread. Aka "deadlock".
To fix this, you need to start the awaiting on a different thread, so that WinForms' synchronization context isn't used - a solution on which you have accidentally stumbled.
An alternative recommended solution is to tell the TPL explicitly not to use the "current" context via Task.ConfigureAwait
:
let result =
func().ConfigureAwait( continueOnCapturedContext = false )
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
Unfortunately, this will not compile, because Async.AwaitTask
expects a Task
, and ConfigureAwait
returns a ConfiguredTaskAwaitable
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With