6

Suppose I have some C# code that takes a callback:

void DoSomething(Action<string> callback);

Now, I want to use this in F#, but wrap it in an async. How would I go about this?

// Not real code
let doSomething = async {
 let mutable result = null
 new Action(fun x -> result <- x) |> Tasks.DoSomething
 // Wait for result to be assigned
 return result
}

For example, suppose DoSomething looks like this:

module Tasks
 let DoSomething callback = 
 callback "Hello"
 ()

Then the output of the following should be "Hello":

let wrappedDoSomething = async {
 // Call DoSomething somehow
}
[<EntryPoint>]
let main argv =
 async {
 let! resultOfDoSomething = wrappedDoSomething
 Console.WriteLine resultOfDoSomething
 return ()
 } |> Async.RunSynchronously
 0
asked Jun 10, 2018 at 10:23
10
  • Is async { callback() } not what you want? Commented Jun 10, 2018 at 13:55
  • 1
    Can you be more clear about exactly what you expect the workflow to look like; i.e. what values should be passed into where, and when? Maybe use more descriptive names than DoSomething and Callback so it's more evident what you're trying to do. Commented Jun 10, 2018 at 19:03
  • 1
    That can only be done by modifying your C# function DoSomething to accept the result you want to send or a function that produces it. You can't just magically make it call the callback with something different from what it's coded to do. You could wrap the callback in another callback that ignores the original parameter and injects something else, but I can't tell if that's what you're really asking for. Commented Jun 10, 2018 at 20:53
  • 1
    Also your example does not follow your question. DoSomething in the example does not have a callback. I don't see how it relates. Commented Jun 10, 2018 at 20:55
  • 2
    @DaxFohl this can very well be done. This callback structure is exactly how Async is implemented inside, you just need to map the callback to Async continuations, and lo: there is an app for that - Async.FromContinuations. Commented Jun 10, 2018 at 21:32

2 Answers 2

7

The function Async.FromContinuations is, so to say, the "lowest level" of Async. All other async combinators can be expressed in terms of it.

It is the lowest level in the sense that it directly encodes the very nature of async computations - the knowledge of what to do in the three possible cases: (1) a successful completion of the previous computation step, (2) a crash of the previous computation step, and (3) cancellation from outside. These possible cases are expressed as the three function-typed arguments of the function that you pass to Async.FromContinuations. For example:

let returnFive = 
 Async.FromContinuations( fun (succ, err, cancl) ->
 succ 5
 )
async {
 let! res = returnFive
 printfn "%A" res // Prints "5"
} 
|> Async.RunSynchronously

Here, my function fun (succ, err, cancl) -> succ 5 has decided that it has completed successfully, and calls the succ continuation to pass its computation result to the next step.

In your case, the function DoSomething expresses only one of the three cases - i.e. "what to do on successful completion". Once you're inside the callback, it means that whatever DoSomething was doing, has completed successfully. That's when you need to call the succ continuation:

let doSometingAsync = 
 Async.FromContinuations( fun (succ, err, cancl) -> 
 Tasks.DoSomething( fun res -> succ res ) 
 )

Of course, you can avoid a nested lambda-expression fun res -> succ res by passing succ directly into DoSomething as callback. Unfortunately, you'll have to explicitly specify which type of Action to use for wrapping it, which negates the advantage:

let doSometingAsync = 
 Async.FromContinuations( fun (succ, err, cancl) -> 
 Tasks.DoSomething( System.Action<string> succ ) 
 )

As an aside, note that this immediately uncovered a hole in the DoSomething's API: it ignores the error case. What happens if DoSomething fails to do whatever it was meant to do? There is no way you'd know about it, and the whole async workflow will just hang. Or, even worse: the process will exit immediately (depending on how the crash happens).

If you have any control over DoSomething, I suggest you address this issue.

answered Jun 10, 2018 at 20:31
Sign up to request clarification or add additional context in comments.

4 Comments

Well being C#, the error case is probably an Exception. A try-with that calls the err function could implement this.
No, it probably isn't an exception. This style of API points towards an asychronous nature of the function. That is, when you call DoSomethiing, it starts a background worker thread and immediately returns. If a failure occurs, it would be on the background thread. And since you don't control the entry point of that thread, there is nowhere to catch the exception.
Yes, good point. More realistic would be a second callback; an error parameter to the callback; or an error-value like null. Your answer makes it clear how to handle these though.
If my answer helped you, would you consider accepting it?
3

You can try something like:

let doSomething callback = async {
 Tasks.DoSomething(callback)
}

If your goal is to define the callback in the method you could do something like:

let doSomething () = async {
 let callback = new Action<string>(fun result -> printfn "%A" result )
 Tasks.DoSomething(callback)
}

If your goal is to have the result of the async method be used in the DoSomething callback you could do something like:

let doSomething =
 Async.StartWithContinuations(
 async {
 return result
 },
 (fun result -> Tasks.DoSomething(result)),
 (fun _ -> printfn "Deal with exception."),
 (fun _ -> printfn "Deal with cancellation."))
answered Jun 10, 2018 at 17:10

1 Comment

What if I want the result of the async to be the result passed to the callback?

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.