Kind of. It supports the C# Task interface nicely (and has for ages), but it you have a bunch of F# async code then you still have to marry the two somehow, and that's still monstrous.
Or is there a particular case that’s problematic? So far I have not seen any interoperability issues around this (even though yes, async CEs are way less efficient than task CEs) but I’m still learning so additional details would be appreciated.
The two languages have two different async abstractions which at their core aren't compatible in general. When that happens in software, one of several possible options will happen. The thing Microsoft chose here is to give an API that looks like it's compatible but which is subtly broken for a nontrivial fraction of code (other common choices include rejecting any level of compatibility, providing more complicated APIs allowing the user to handle the multi-language nuances, or providing an incomplete API which works as expected).
Much like how when one proposes a perpetual motion machine you know there must be a bug in the math somewhere, when somebody proposes an interop between two incompatible data structures there must also be a drawback. Pick literally any feature of the language other than sequentially executing code (exceptions and threads are usually great ways for languages and specs to fall apart), and you'll likely find places where the interop struggles. A few that come to mind:
- That solution is only one direction. Interoperability needs to go both ways.
- The exception interface between the two constructs is sufficiently poorly defined that I'd call it a footgun, and code that won't wake you up at night usually has to do extra work.
- Performance in that kind of wrapping is a nightmare, both from the overhead of additional runtime layers, and, more importantly, because the last time I checked it was basically "sync over async" in a way that made the type system happy.
- Going back to the exception thing, cancellation tokens are especially poorly handled in that interop one-liner.
- They still haven't fixed blunders like ConfigureAwait(false), and the interop code absolutely has to care about that sort of thing in applicable contexts (the worst offenders being any sort of "main thread owns everything" code like current popular GUI paradigms).
This reads as an AI-generated text that is very emotional but provides few meaningful technical details (no examples, unrelated APIs, ConfigureAwait - really now?).
I'm not sure if this makes me sound more or less artificial, but outside the use of the word "blunder" there were scarcely any strong opinions, much less emotions.
> Few technical details
You came across as having done enough F# and C# to not need a novel to understand. Yes, those APIs are related to the problem of merging tasks and async between the two languages. 95% of the time you won't care, and 5% you'll hit a footgun. Just go write some multilingual GUI async code with a few cancellation tokens and exceptions and report back with the edge cases you find. The two abstractions are sufficiently different that you will necessarily find some, and if you aren't fortunate enough for your F# codebase to be mostly Tasks instead of async the you'll have to actually care.
Okay, sorry. The repeated "monstrous" and focus on how bad it is (which I genuinely disagree with - it's absolutely fine) rubbed me the wrong way.
> and if you aren't fortunate enough for your F# codebase to be mostly Tasks instead of async the you'll have to actually care.
My expectation is that async-based methods are not exposed to consuming from C#. If they are - this is unfortunate. The ideal state is to modernize the implementation and mainly rely on task CEs. Async CE has its advantages in terms implicit cancellation propagation but flowing down a cancellation token explicitly is usually a more pleasant experience than in other languages. Nothing that stands out as monstrous, and if something is - it has to do with the way a particular GUI framework implements it and not the language and platform themselves.
Last note on sync over async - threadpool can deal with it just fine. It has detection mechanism for this which proactively injects threads when a worker thread is expected to be blocked that bypasses regular scaling through hill-climbing.
Which works fine assuming all new development is happening in the F# half of a codebase and you don't have a significant amount of old F# async code lying around.
It doesn't require that. You can do a strangler pattern of sorts and use task for the interop layer, then run each call site through a small helper that converts to F# async.
Or, hell, just throw caution to the wind and text replace. I've heard some folks do that and it worked out well for them + wasn't much time to clean up either. They're especially happy to have better stack traces too.
Asynchronous sequences (IAsyncEnumerable) support is provided via https://github.com/fsprojects/FSharp.Control.TaskSeq
F# is a very capable language - a broad spectrum of tasks can be solved by just implementing a new CE, something I could not even think of in C#.