6

Is it possible to wait (without blocking) on an Async<'t> within an Akka.Net actor computation? I want to achieve something similar to the following.

actor {
  let! msg = mailbox.Receive()
  match msg with
  | Foo ->
    let! x = async.Return "testing 123" // Some async function, return just an example
    () // Do something with result
}
Oenotria
  • 1,692
  • 11
  • 24
  • Based on this: https://github.com/akkadotnet/akka.net/blob/93207aa37498edea0b7c319f59ef20e07fc9fd62/src/core/Akka.FSharp/FsApi.fs it seems af if Akka.net choose to reimblemend their own AsyncBuilder right there ... so no you cannot use F#'s Asyncs helpers right there (no Monad-Transformers in F#) - of course you can try to reimblement what you need into Akka's `Cont` (using Async.StartWithContinuations) – Random Dev Aug 09 '14 at 15:21

2 Answers2

4

No you can't use async / await or any variant thereof inside an actor's mailbox and get safe results.

Each actor maintains its own context, which includes important details like the Sender of the previous message and other important pieces of state which might change. Actors process messages serially, so as soon as the call inside its mailbox finishes it immediately begins processing its next message - if you put an await call inside the mailbox, the actor will be processing a totally different message than the one you started with by the time your await call returns.

A better pattern for leveraging asynchronous calls and the TAP inside actors is to use the PipeTo pattern. It looks like we don't have any documentation for this yet at http://akkadotnet.github.io/, so I'll give you a real-world code sample (in C#):

    public void Handle(ExplicitReplyOperation<CampaignsForAppRequest> message)
    {
        Context.IncrementMessagesReceived();
        _loaderActor.Ask(message.Data).ContinueWith(r =>
        {
            var campaigns = (IList<Campaign>)r.Result;
            message.Originator.Tell(new CampaignsForAppResponse()
            {
                AppId = message.Data.AppId,
                ActiveCampaigns = campaigns
            }, ActorRef.NoSender);
            return campaigns;
        }).PipeTo(Self);
    }

In this sample I have a TypedActor that continues a Task, does some post-processing, and then uses the PipeTo operator (an Akka.NET extension method you can apply to any Task object) to pipe the results of the task into this actor's mailbox once the operation is finished. That way I can close over any of the state I need and my actor can continue processing messages in a safe way while this async operation continues.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Aaronontheweb
  • 8,224
  • 6
  • 32
  • 61
  • Thanks. This works but it's not ideal as it makes the code much harder to read. – Oenotria Aug 10 '14 at 14:25
  • @Oenotria there is an issue for this in the Akka.NET tracker https://github.com/akkadotnet/akka.net/issues/44 , the problem is however quite tricky to solve, the mailbox and the statemachines that make up the async await code need to interact (somehow) – Roger Johansson Aug 10 '14 at 17:43
  • What is the reason behind using the custom actor{} computation expression? Why not just use async{} like the standard F# MailboxProcessor? – Oenotria Aug 11 '14 at 08:10
  • 3
    It builds an expression that can be remote deployed, that is, the actor code in the actor builder can be pushed to remote nodes, just like erlang.. normal async builder can not do this. – Roger Johansson Aug 12 '14 at 06:43
3

This now seems to be possible!

let system = ConfigurationFactory.Default() |> System.create "FSharpActors"
let asyncActor =
  spawn system "MyActor"
  <| fun mailbox ->
    let rec loop() =
      actor {
      let! name = mailbox.Receive()
      Akka.Dispatch.ActorTaskScheduler.RunTask(fun () ->
      async {
        printfn "Hello %s" name
        do! Async.Sleep 5000
        } |> Async.StartAsTask :> Threading.Tasks.Task)
      return! loop()
      }

    loop()

asyncActor <! "Alice"
asyncActor <! "Bob"
asyncActor <! "Eve"
Oenotria
  • 1,692
  • 11
  • 24