C# 针对调度程序将异步等待C代码转换为F代码

C# 针对调度程序将异步等待C代码转换为F代码,c#,f#,task-parallel-library,c#-to-f#,orleans,C#,F#,Task Parallel Library,C# To F#,Orleans,我不知道这是否是一个太宽泛的问题,但最近我遇到了一段代码,我想确定如何将C#翻译成适当的F#。旅程从(TPL-F#交互的原始问题)开始,然后继续(我正在考虑将一些示例代码翻译成F#) 示例代码太长,无法在此处重现,但有趣的函数有ActivateAsync、RefreshHubs和AddHub。特别有趣的是 AddHub的签名为private async Task AddHub(字符串地址) RefreshHubs在循环中调用AddHub,并收集任务列表,然后在最后通过wait Task等待任务。

我不知道这是否是一个太宽泛的问题,但最近我遇到了一段代码,我想确定如何将C#翻译成适当的F#。旅程从(TPL-F#交互的原始问题)开始,然后继续(我正在考虑将一些示例代码翻译成F#)

示例代码太长,无法在此处重现,但有趣的函数有
ActivateAsync
RefreshHubs
AddHub
。特别有趣的是

  • AddHub
    的签名为
    private async Task AddHub(字符串地址)
  • RefreshHubs
    在循环中调用
    AddHub
    ,并收集
    任务列表
    ,然后在最后通过
    wait Task等待任务。当所有(任务)
    时,返回值与
    私有异步任务RefreshHubs(对象)
    的签名匹配
  • ActivateAsync
    调用
    RefreshHubs
    ,就像调用
    await RefreshHubs(null)
    一样,最后调用
    await base.ActivateAsync()
    匹配函数签名
    公共覆盖异步任务ActivateAsync()
  • 问题:

    将这些函数签名正确地转换为F#会是什么?F#仍然保持接口和功能,并尊重默认的自定义调度程序?另外,我也不太确定这种“异步/在F#中等待”。就像如何“机械地”完成一样:)

    原因是,在链接“here(1)”中似乎存在问题(我还没有验证这一点),因为F#异步操作不遵守(Orleans)运行时设置的自定义协作调度程序。此外,还声明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
    

    您可以在FSharpx中使用
    TaskBuilder
    ,并传入
    TaskScheduler.Current
    。下面是我翻译
    RefreshHubs
    的尝试。请注意,
    Task
    用于代替
    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 ()
        }
    
    让我们来看看=
    让task=TaskBuilder(scheduler=TaskScheduler.Current)
    任务{
    让地址=
    RoleEnvironment.Roles.[“GPSTracker.Web”].Instances
    |>Seq.map(趣味实例->
    让endpoint=instance.InstanceEndpoints。[“InternalSignaler”]
    sprintf“http://%O”endpoint.IPEndpoint
    )
    |>序号:toList
    让newHubs=addresses |>List.filter(而不是Seq.filter(fun x->
    非(List.exists(=)x)地址)
    //移除死毂
    deadHubs |>Seq.iter(hubs.Remove>>忽略)
    //添加新的集线器
    let!=Task.WhenAll[|对于新集线器中的集线器->添加集线器集线器|]
    返回()
    }
    
    我没有使用过奥尔良,但是如果它需要在自定义任务调度器上运行所有异步代码,那么用F#编码它将是一件令人头痛的事,因为
    异步
    不支持这一点。您可以做到最好(AFAIK)将每个
    任务
    转换为
    异步确实,这就是我试图做的,只是避免异步工作流。唉,我不确定我是否成功,无论如何,我对如何将某些C#模式转换为F#有点不确定(例如,我应该使用
    异步{}
    ,如果是,如何使用,或者如果不是,再次使用).通常我用这两种语言都写,我没有必要认真思考。我不知道如何评论这一部分,但我只是想指出我认为您的“here(2)”中原始C代码中存在的一些错误示例。
    Flush
    需要是一个
    async Task
    方法,而不是
    void
    ,因为
    HubConnection.Start
    IHupProxy.Invoke
    都是返回
    Task
    的异步方法,应该等待。您可能希望在通过foreach循环并执行任务时收集任务。在泛滥方法的d。我会考虑编写一个自定义生成器(一个用于<代码>异步的<代码> >),它直接与<代码>任务<代码>一起工作,而不是<代码>异步> <代码>,并使用当前<代码> TaskStalpder < /C> >继续运行。@ Svik:这是一个很好的建议,这是一个很好的尝试。(C#代码有点“哦,不”,包含了所有“void FunctionMutatesInternalState()”之类的东西。即使在C#和类中,传入参数、收集返回值并将其分配到某个位置也会让人感觉更容易。哦,这是一次很好的运行。谢谢!
    type TaskBuilder(?continuationOptions, ?scheduler, ?cancellationToken) =
        let contOptions = defaultArg continuationOptions TaskContinuationOptions.None
        let scheduler = defaultArg scheduler TaskScheduler.Default
        let cancellationToken = defaultArg cancellationToken CancellationToken.None
    
    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 ()
        }