将异步 - 等待C#代码转换为与调度程序相关的F#

时间:2014-07-17 20:55:14

标签: c# f# task-parallel-library c#-to-f# orleans

我想知道这是否是一个太广泛的问题,但最近我让自己遇到了一段我想确定如何从C#转换为适当的F#的代码。旅程从here (1)(TPL-F#交互的原始问题)开始,并继续here (2)(我正在考虑转换为F#的一些示例代码)。

示例代码在此处重现的时间太长,但有趣的函数是ActivateAsyncRefreshHubsAddHub。特别有趣的是

  1. AddHub的签名为private async Task AddHub(string address)
  2. RefreshHubs在循环中调用AddHub并收集tasks的列表,然后由await Task.WhenAll(tasks)在最后等待,因此返回值与其签名匹配private async Task RefreshHubs(object _)
  3. RefreshHubsActivateAsyncawait RefreshHubs(null)一样调用,最后有一个与函数签名await base.ActivateAsync()匹配的调用public override async Task ActivateAsync()
  4. 问题:

    这些功能签名到F#的正确翻译是什么,它仍然保持接口和功能,并且尊重默认的自定义调度程序?而且我也不太确定这个“在F#中异步/等待”。至于如何“机械地”做到这一点。 :)

    原因是在链接“here(1)”中似乎存在问题(我没有验证过),因为F#异步操作不遵循(Orleans)运行时设置的自定义协作调度程序。此外,它声明here TPL操作逃离调度程序并转到任务池,因此禁止使用它们。

    我能想到解决这个问题的一种方法是使用F#函数,如下所示

    //Sorry for the inconvenience of shorterned code, for context see the link "here (1)"...
    override this.ActivateAsync() =
        this.RegisterTimer(new Func<obj, Task>(this.FlushQueue), null, TimeSpan.FromMilliseconds(100.0), TimeSpan.FromMilliseconds(100.0)) |> ignore
    
        if RoleEnvironment.IsAvailable then
            this.RefreshHubs(null) |> Async.awaitPlainTask |> Async.RunSynchronously
        else
            this.AddHub("http://localhost:48777/") |> Async.awaitPlainTask |> Async.RunSynchronously
    
        //Return value comes from here.
        base.ActivateAsync()
    
    member private this.RefreshHubs(_) =
        //Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience...
        //The return value is Task.
        //In the C# version the AddHub provided tasks are collected and then the
        //on the last line there is return await Task.WhenAll(newHubAdditionTasks) 
        newHubs |> Array.map(fun i -> this.AddHub(i)) |> Task.WhenAll
    
    member private this.AddHub(address) =
        //Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience...
        //In the C# version:
        //...
        //hubs.Add(address, new Tuple<HubConnection, IHubProxy>(hubConnection, hub))
        //} 
        //so this is "void" and could perhaps be Async<void> in F#... 
        //The return value is Task.
        hubConnection.Start() |> Async.awaitTaskVoid |> Async.RunSynchronously
        TaskDone.Done
    

    startAsPlainTask功能来自here Sacha Barber 。另一个有趣的选择可能是here

    module Async =
        let AwaitTaskVoid : (Task -> Async<unit>) =
            Async.AwaitIAsyncResult >> Async.Ignore
    

    &lt; edit:我刚才注意到Task.WhenAll也需要等待。但是什么是正确的方法?呃,是时候睡觉了(一个不好的双关语)...

    &lt;编辑2:在Codeplex中here (1)(TPL-F#交互的最初问题),提到F#使用同步上下文将工作推送到线程,而TPL确实不。现在,这是一个看似合理的解释,我觉得(尽管无论自定义调度程序如何,我仍然无法正确翻译这些代码段)。可以从

    获得一些有趣的附加信息

    我认为我需要在这个背景下提及Hopac,作为一个有趣的切入点,并且还提到我在接下来的50个小时左右是不可及的,以防我的所有交叉发布都失控

    &lt; edit 3 Danielsvick在评论中提供了很好的建议,以便使用自定义任务构建器。 Daniel提供了一个已经在FSharpx中定义的链接的链接。

    查看源代码,我看到带参数的接口定义为

    type TaskBuilder(?continuationOptions, ?scheduler, ?cancellationToken) =
        let contOptions = defaultArg continuationOptions TaskContinuationOptions.None
        let scheduler = defaultArg scheduler TaskScheduler.Default
        let cancellationToken = defaultArg cancellationToken CancellationToken.None
    

    如果要在奥尔良使用此功能,根据文档here

    TaskScheduler看起来应该是TaskScheduler.Current
      

    Orleans拥有自己的任务调度程序,它提供单线程   谷物中使用的执行模型。跑步时很重要   使用Orleans调度程序的任务,而不是.NET线程池。

         

    如果您的谷物代码需要创建子任务,您应该使用   Task.Factory.StartNew:

         

    await Task.Factory.StartNew(()=&gt; {/ * logic * /});

         

    这种技术将使用当前的任务调度程序,它将是   奥尔良调度员。

         

    您应该避免使用始终使用.NET线程的Task.Run   池,因此不会在单线程执行中运行   模型。

    看起来TaskScheduler.CurrentTaskScheduler.Default之间存在细微差别。也许这可以保证一个问题,在哪种情况下会出现意想不到的差异。由于奥尔良文档指出不使用Task.Run而是引导Task.Factory.StartNew,我想知道是否应该按照 Stephen Toub等权威机构的建议来定义TaskCreationOptions.DenyAttachChild Task.Run vs Task.Factory.StartNew和 Stephen Cleary StartNew is Dangerous。嗯,看起来.Default将是.DenyAttachChilld,除非我弄错了。

    此外,由于Task.Run viz Task.Factory.CreateNew存在关于自定义调度程序的问题,我想知道是否可以使用自定义TaskFactory删除此特定问题,如{{{ 3}}和Task Scheduler (Task.Factory) and controlling the number of threads

    嗯,这已经成为一个漫长的“思考”。我想知道我该如何关闭它?也许如果 svick Daniel 可以将他们的评论作为答案,我会同时投票并接受 svick's

1 个答案:

答案 0 :(得分:1)

您可以在FSharpx中使用TaskBuilder并传入TaskScheduler.Current。我试图翻译RefreshHubs。请注意,Task<unit>代替Task

let RefreshHubs _ =
    let task = TaskBuilder(scheduler = TaskScheduler.Current)
    task {
        let addresses = 
            RoleEnvironment.Roles.["GPSTracker.Web"].Instances
            |> Seq.map (fun instance ->
                let endpoint = instance.InstanceEndpoints.["InternalSignalR"]
                sprintf "http://%O" endpoint.IPEndpoint
            )
            |> Seq.toList

        let newHubs = addresses |> List.filter (not << hubs.ContainsKey)
        let deadHubs = hubs.Keys |> Seq.filter (fun x -> 
            not (List.exists ((=) x) addresses))

        // remove dead hubs
        deadHubs |> Seq.iter (hubs.Remove >> ignore)

        // add new hubs
        let! _ = Task.WhenAll [| for hub in newHubs -> AddHub hub |]
        return ()
    }