Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consuming F# Async from C#

Tags:

async-await

f#

Suppose that I have the following code:

namespace Library1

open System.Threading.Tasks
open System.Threading
open System.Runtime.Remoting.Messaging
open System

type public Class1() =

    let printThread (message) = 
        printfn "%s %A" message Thread.CurrentThread.ManagedThreadId

    let bar = 
        printThread ("first bar")
        async { 
            printThread ("first async")
            do! Async.Sleep(1000)
            printThread "last async"
        }

    member this.X() =  bar

I would like to use this class and invoke X from C#. The problem is X returns an Async<'T>. However, it is bad practice to expose F# specific types. So best practice is to return a Task. However Async.StartAsTask is kind of problematic since it will cause the code run in a seperate thread. What I want is I want to return a Task but also it should behave as Async.StartImmediate. Thus the non async part of the code should run in the original main thread. Here I assume I run it from UI thread so that all calls will return the same thread ID. In other words I want a Async.StartImmediate but returning a task. Is this achieavable ?

like image 696
Onur Gumus Avatar asked Apr 01 '14 11:04

Onur Gumus


2 Answers

You can turn Async<'T> into Task<'T> using the Async.StartAsTask<'T> method.

I'd generally recommend making things easy for the C# users and extend the F# implementation with an additional method that returns Task<'T>. Following the usual naming convention, you can call the F# version AsyncFoo and the C#-friendly version FooAsync.

Looking at your example, I'd go with something like this:

type public Class1() =

    let printThread (message) = 
        printfn "%s %A" message Thread.CurrentThread.ManagedThreadId

    let bar = 
        printThread ("first bar")
        async { 
            printThread ("first async")
            do! Async.Sleep(1000)
            printThread "last async"
        }

    member this.AsyncFoo() =  bar

    /// Expose C#-friendly asynchronous method that returns Task
    member this.FooAsync() = Async.StartAsTask(bar)
    /// Expose C#-friendly asynchronous method that returns Task
    /// and takes cancellation token to support cancellation...
    member this.FooAsync(cancellationToken) = 
      Async.StartAsTask(bar, ?cancellationToken=cancellationToken)
like image 140
Tomas Petricek Avatar answered Oct 13 '22 08:10

Tomas Petricek


This Works exactly as I want (unlike the question this version returns an int as well which is plus):

type public Class1() = 
    let printThread (message) = printfn "%s %A" message Thread.CurrentThread.ManagedThreadId

    let bar = 
        printThread ("first bar")
        async { 
            printThread ("first async")
            do! Async.Sleep(1000)
            printThread "last async"
            return 1232
         }

    member this.convertToTask<'T> (asyn : Async<'T>) = 
       let tcs1 = new TaskCompletionSource<'T>()
       let t1 = tcs1.Task
       Async.StartWithContinuations
        (
          asyn 
          , (fun (k) -> tcs1.SetResult(k)), (fun exn -> tcs1.SetException(exn)), fun exn -> ())
        t1

    member this.X() : Task<int> =  (bar |> this.convertToTask)
like image 41
Onur Gumus Avatar answered Oct 13 '22 09:10

Onur Gumus