-
Notifications
You must be signed in to change notification settings - Fork 830
Description
MailboxProcessor (MP) provides a convenient mechanism to implement message loops. I sometimes use it in console apps to implement the main application loop, which starts the MP and then waits until MP "pumps" all messages. Since the main thread has nothing else to do, I would like it to run the MP synchronously. Unfortunately MP does not provide an equivalent of Async.RunSynchronously function.
There are other cases where it'd make sense to run MP in the current thread or a specific execution context. e.g. when using it in a worker thread. While simulating synchronous invocation is easy, e.g. by calling PostAndReply, the computation itself is always forced to run on the thread pool, introducing an unnecessary overhead.
This behavior is due to the MailboxProcessor.Start method directly calling Async.Start. As a result the caller has no control over computation execution context. (Well, it's possible to control it using Async.SwitchTo*(...) within the computation, but it'd complicate the code)
One simple way to make MP more flexible is to add MailboxProcessor.StartWith method accepting async computation "starter" function as a parameter.
Specifically, in the MP type definition, change
member x.Start() =
...
Async.Start(computation=p, cancellationToken=cancellationToken)
to
member x.StartWith(startAsync: Async<unit> -> CancellationToken -> unit) =
...
startAsync p cancellationToken
and then reimplement the original Start method as follows:
member x.Start() = x.StartWith (fun c ct -> Async.Start(computation=c, cancellationToken=ct))
It will then be possible to call the new method with Async.Start, Async.RunSynchronously, or any other function having an appropriate signature:
// run on the thread pool - mimic the current implementation
mbox.StartWith (fun c ct -> Async.Start(computation=c, cancellationToken=ct))
// start on the current thread
mbox.StartWith (fun c ct -> Async.StartImmediate(computation=c, cancellationToken=ct))
// run synchronously
mbox.StartWith (fun c ct -> Async.RunSynchronously(computation=c, cancellationToken=ct))
// run in a user-specified context
mbox.StartWith customAsyncStarter
...
Also, the following convenience member can be added to the class, to complement the static member Start(...):
static member StartWith(body, startAsync, ?cancellationToken) =
let mailboxProcessor = new MailboxProcessor<'Msg>(body, ?cancellationToken=cancellationToken)
mailboxProcessor.StartWith(startAsync)
mailboxProcessor
As you can see, the proposed change is fairly small, fully backward-compatible, makes MailboxProcessor more flexible and, hopefully, usable in more contexts. If you find it acceptable, I will be glad to send a PR implementing the change.