navigation

NoAsyncRunSynchronouslyInLibrary (FL0090)

Introduced in 0.26.12

Cause

Async.RunSynchronously method is used to run async computation in library code.

The rule assumes the code is in the library if none of the following is true:

  • The code is inside NUnit or MSTest test.
  • Namespace or project name contains "test" or "console".
  • Assembly has [<EntryPoint>] attribute one one of the functions/methods.

Rationale

Your library code might be consumed by certain type of programs which have strict threading requirements (e.g. a long running operation shouldn't be run on the main thread of a desktop app, or it will make the app look like it's hanging for a while), so it's better to expose asynchronous code with Async<'TResult> or Task/Task<'TResult> return types, so that the consumer of your library can decide how/when to start the operation.

How To Fix

Remove Async.RunSynchronously and wrap the code that uses async computations in async computation, using let!, use!, match!, or return! keyword to get the result.

Example:

type SomeType() =
    member self.SomeMethod someParam =
        let foo =
            asyncSomeFunc someParam
            |> Async.RunSynchronously
        processFoo foo

The function can be modified to be asynchronous. In that case it might be better to prefix its name with Async:

type SomeType() =
    member self.AsyncSomeMethod someParam = async {
        let! foo = asyncSomeFunc someParam
        return processFoo foo
    }

In case the method/function is public, a nice C#-friendly overload that returns Task<'T> could be provided, suffixed with Async, that just calls the previous method with Async.StartAsTask:

type SomeType() =
    member self.AsyncSomeMethod someParam = async {
        let! foo = asyncSomeFunc someParam
        return processFoo foo
    }
    member self.SomeMethodAsync someParam =
        self.AsyncSomeMethod someParam
        |> Async.StartAsTask

Rule Settings

{
    "noAsyncRunSynchronouslyInLibrary": {
        "enabled": true
    }
}