let fooAsync() = async {
do! Async.Sleep(1000) }
let handler() =
fooAsync() |> Async.RunSynchronously
You could certainly write such code by accident, but if you face a problem that it does
not run asynchronously, you can easily spot that the code calls
RunSynchronously and so the work is done - as the name suggests - synchronously.
Summary
In this article, I looked at six cases where the C# asynchronous programming model
behaves in an unexpected way. Most of them were based on a talk by Lucian and Stephen
at the MVP summit, so thanks to both of them for sharing an interesting list of common
pitfalls!
I tried to find the closest corresponding code snippet in F#, using asynchronous workflows.
In most of the cases, the F# compiler reports a warning or an error - or the programming
model does not have a (direct) way to express the same code. I think this supports the
claim that I made in an earlier blog post that "The F# programming model definitely
feels more suitable for functional (declarative) programming languages. I also think that it
makes it easier to reason about what is going on".
Finally, this article should not be understood as a devastating criticism of C# async :-). I can
fully understand why the C# design follows the principles it follows - for C#, it makes
sense to use Task<T> (instead of separate Async<T>), which has a number of implications.
And I can understand the reasoning behind other decisions too - it is likely the best way
to integrate asynchronous programming in C#. But at the same time, I think F# does a better
job - partly because of the composability, but more importantly because of greate additions
like the F# agents. Also, F# async has its problems too (the most common gotcha
is that tail-recursive functions must use return! instead of do! to avoid leaks), but
that is a topic for a separate blog post.
namespace System
namespace System.Threading
namespace System.Threading.Tasks
val workThenWait : unit -> Async<unit>
Multiple items
type Thread =
inherit CriticalFinalizerObject
new : start:ThreadStart -> Thread + 3 overloads
member Abort : unit -> unit + 1 overload
member ApartmentState : ApartmentState with get, set
member CurrentCulture : CultureInfo with get, set
member CurrentUICulture : CultureInfo with get, set
member DisableComObjectEagerCleanup : unit -> unit
member ExecutionContext : ExecutionContext
member GetApartmentState : unit -> ApartmentState
member GetCompressedStack : unit -> CompressedStack
member GetHashCode : unit -> int
...
--------------------
Thread(start: ThreadStart) : Thread
Thread(start: ParameterizedThreadStart) : Thread
Thread(start: ThreadStart, maxStackSize: int) : Thread
Thread(start: ParameterizedThreadStart, maxStackSize: int) : Thread
Thread.Sleep(timeout: TimeSpan) : unit
Thread.Sleep(millisecondsTimeout: int) : unit
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
val async : AsyncBuilder
Multiple items
type Async =
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member Choice : computations:seq<Async<'T option>> -> Async<'T option>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
...
--------------------
type Async<'T> =
static member Async.Sleep : millisecondsDueTime:int -> Async<unit>
val demo : unit -> unit
val work : Task<unit>
static member Async.StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
Task.Wait() : unit
Task.Wait(millisecondsTimeout: int) : bool
Task.Wait(cancellationToken: CancellationToken) : unit
Task.Wait(timeout: TimeSpan) : bool
Task.Wait(millisecondsTimeout: int, cancellationToken: CancellationToken) : bool
val handler : unit -> Async<unit>
val throwExceptionAsync : unit -> Async<unit>
val raise : exn:Exception -> 'T
Multiple items
type InvalidOperationException =
inherit SystemException
new : unit -> InvalidOperationException + 2 overloads
--------------------
InvalidOperationException() : InvalidOperationException
InvalidOperationException(message: string) : InvalidOperationException
InvalidOperationException(message: string, innerException: exn) : InvalidOperationException
val callThrowExceptionAsync : unit -> unit
static member Async.Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
val e : exn
type Parallel =
static member For : fromInclusive:int * toExclusive:int * body:Action<int> -> ParallelLoopResult + 11 overloads
static member ForEach<'TSource> : source:IEnumerable<'TSource> * body:Action<'TSource> -> ParallelLoopResult + 19 overloads
static member Invoke : [<ParamArray>] actions:Action[] -> unit + 1 overload
Parallel.For(fromInclusive: int64, toExclusive: int64, body: Action<int64,ParallelLoopState>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int, toExclusive: int, body: Action<int,ParallelLoopState>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int64, toExclusive: int64, body: Action<int64>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int, toExclusive: int, body: Action<int>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int64, toExclusive: int64, parallelOptions: ParallelOptions, body: Action<int64,ParallelLoopState>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int, toExclusive: int, parallelOptions: ParallelOptions, body: Action<int,ParallelLoopState>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int64, toExclusive: int64, parallelOptions: ParallelOptions, body: Action<int64>) : ParallelLoopResult
(+0 other overloads)
Parallel.For(fromInclusive: int, toExclusive: int, parallelOptions: ParallelOptions, body: Action<int>) : ParallelLoopResult
(+0 other overloads)
Parallel.For<'TLocal>(fromInclusive: int64, toExclusive: int64, localInit: Func<'TLocal>, body: Func<int64,ParallelLoopState,'TLocal,'TLocal>, localFinally: Action<'TLocal>) : ParallelLoopResult
(+0 other overloads)
Parallel.For<'TLocal>(fromInclusive: int, toExclusive: int, localInit: Func<'TLocal>, body: Func<int,ParallelLoopState,'TLocal,'TLocal>, localFinally: Action<'TLocal>) : ParallelLoopResult
(+0 other overloads)
val i : 'a
val i : int
Multiple items
type Task =
new : action:Action -> Task + 7 overloads
member AsyncState : obj
member ConfigureAwait : continueOnCapturedContext:bool -> ConfiguredTaskAwaitable
member ContinueWith : continuationAction:Action<Task> -> Task + 19 overloads
member CreationOptions : TaskCreationOptions
member Dispose : unit -> unit
member Exception : AggregateException
member GetAwaiter : unit -> TaskAwaiter
member Id : int
member IsCanceled : bool
...
--------------------
type Task<'TResult> =
inherit Task
new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
member ConfigureAwait : continueOnCapturedContext:bool -> ConfiguredTaskAwaitable<'TResult>
member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 19 overloads
member GetAwaiter : unit -> TaskAwaiter<'TResult>
member Result : 'TResult
static member Factory : TaskFactory<'TResult>
--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
--------------------
Task(function: Func<'TResult>) : Task<'TResult>
Task(function: Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
Task(function: Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(function: Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(function: Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
Task(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Multiple items
property Task.Factory: TaskFactory<'TResult>
--------------------
property Task.Factory: TaskFactory
Multiple items
TaskFactory.StartNew(function: Func<'TResult>) : Task<'TResult>
TaskFactory.StartNew(function: Func<obj,'TResult>, state: obj) : Task<'TResult>
TaskFactory.StartNew(function: Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
TaskFactory.StartNew(function: Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
TaskFactory.StartNew(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
TaskFactory.StartNew(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
TaskFactory.StartNew(function: Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions, scheduler: TaskScheduler) : Task<'TResult>
TaskFactory.StartNew(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions, scheduler: TaskScheduler) : Task<'TResult>
--------------------
TaskFactory.StartNew<'TResult>(function: Func<'TResult>) : Task<'TResult>
(+0 other overloads)
TaskFactory.StartNew(action: Action) : Task
(+0 other overloads)
TaskFactory.StartNew<'TResult>(function: Func<obj,'TResult>, state: obj) : Task<'TResult>
(+0 other overloads)
TaskFactory.StartNew<'TResult>(function: Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
(+0 other overloads)
TaskFactory.StartNew<'TResult>(function: Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
(+0 other overloads)
TaskFactory.StartNew(action: Action<obj>, state: obj) : Task
(+0 other overloads)
TaskFactory.StartNew(action: Action, creationOptions: TaskCreationOptions) : Task
(+0 other overloads)
TaskFactory.StartNew(action: Action, cancellationToken: CancellationToken) : Task
(+0 other overloads)
TaskFactory.StartNew<'TResult>(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
(+0 other overloads)
TaskFactory.StartNew<'TResult>(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
(+0 other overloads)
static member Async.AwaitTask : task:Task -> Async<unit>
static member Async.AwaitTask : task:Task<'T> -> Async<'T>
val fooAsync : unit -> Async<unit>
val handler : unit -> unit
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
Published: Monday, 15 April 2013, 4:00 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: async, c#, f#
License and about
All articles on this site are licensed under Creative Commons Attribution Share Alike.
All source code samples are licensed under the MIT License.
This site is hosted on GitHub and is generated using F# Formatting
and DotLiquid.
For more info, see the website source on GitHub.
Please submit issues & corrections on GitHub. Use pull requests for minor corrections only.