翻译异步-等待C#代码到F#相对于所述调度 [英] Translating async-await C# code to F# with respect to the scheduler

查看:251
本文介绍了翻译异步-等待C#代码到F#相对于所述调度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不知道这是一个过于广泛的问题,但最近我自己就碰到过一段代码,我想就如何从C#转换成正确的F#肯定。旅程从这里(1)(原问题TPL-F#交互)开始,并继续这里(2)(例如一些代码我考虑转化为F#)。

I wonder if this is too a broad question, but recently I made myself to come across a piece of code I'd like to be certain on how to translate from C# into proper F#. The journey starts from here (1) (the original problem with TPL-F# interaction), and continues here (2) (some example code I'm contemplating to translate into F#).

该示例代码太长,在这里重现,但有趣的功能是 ActivateAsync RefreshHubs AddHub 。特别有趣的点是

The example code is too long to reproduce here, but the interesting functions are ActivateAsync, RefreshHubs and AddHub. Particularly the interesting points are


  1. AddHub 有一个签名私人异步任务AddHub(字符串地址)

  2. RefreshHubs 通话 AddHub 在一个循环和收集的任务,它然后在最后一刻等待列表等待Task.WhenAll(任务)异步任务RefreshHubs(对象_)。

  3. 的签名相匹配的私人> RefreshHubs ActivateAsync 正如等待RefreshHubs(空)然后在年底有一个叫等待base.ActivateAsync()匹配函数签名公众覆盖异步任务ActivateAsync()
  1. AddHub has a signature of private async Task AddHub(string address).
  2. RefreshHubs calls AddHub in a loop and collects a list of tasks, which it then awaits in the very end by await Task.WhenAll(tasks) and consequently the return value matches its signature of private async Task RefreshHubs(object _).
  3. RefreshHubs is called by ActivateAsync just as await RefreshHubs(null) and then in the end there's a call await base.ActivateAsync() matching the function signature public override async Task ActivateAsync().

问:

什么是这样的函数签名,F#仍保持了界面和功能,并尊重默认,自定义调度正确的翻译?而且我没有其他不太清楚这一点异步F#中/等待两种。作为该怎么办呢机械。 :)

What would be the correct translation of such function signatures to F# that still maintains the interface and functionality and respects the default, custom scheduler? And I'm not otherwise too sure of this "async/await in F#" either. As in how to do it "mechanically". :)

究其原因是,在链接点击这里(1)似乎有问题(我还没有证实这一点)在F#异步操作不尊重习俗,合作的调度​​由(新奥尔良)运行时设置。此外,它的既定这里是TPL操作逃脱调度并转至任务池,因此禁止其使用​​。

The reason is that in the link "here (1)" there seem to be problem (I haven't verified this) in that F# async operations do not respect a custom, cooperative scheduler set by the (Orleans) runtime. Also, it's stated here that TPL operations escape the scheduler and go to the task pool and their use is therefore prohibited.

我能想到处理这个问题的方法之一是用F#函数如下:

One way I can think of dealing with this is with a F# function as follows

//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 功能是通过萨沙理发来自的此处。另一个有趣的选择可能是这里

The startAsPlainTask function is by Sacha Barber from here. Another interesting option could be here as

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



<编辑:我只注意到了 Task.WhenAll 将需要等待太。但是,什么是正确的方法是什么?呃,睡觉的时间(一个糟糕的双关语)...

<edit: I just noticed the Task.WhenAll would need to be awaited too. But what would be the proper way? Uh, time to sleep (a bad pun)...

<编辑2:在的这里(1)在 Codeplex上(与TPL-F#交互原问题)中提到,F#使用同步上下文来推动工作线程,而TPL没有。现在,这是一个合理的解释,我觉得(虽然我还是有把这些片段问题妥善无论自定义调度)。一些有趣的附加信息可以从

<edit 2: At here (1) (the original problem with TPL-F# interaction) in Codeplex it was mentioned that F# uses synchronization contexts to push work to threads, whereas TPL does not. Now, this is a plausible explanation, I feel (although I'd still have problems in translating these snippets properly regardless of the custom scheduler). Some interesting additional information could be to had from

  • How to get a Task that uses SynchronizationContext? And how are SynchronizationContext used anyway?
  • Await, SynchronizationContext, and Console Apps wherein an example SingleThreadSynchronizationContext is provided that looks like queues the work to be executed. Maybe this ought to be used?

我想我需要提到的 Hopac 在此背景下,作为一个有趣的切向和还提到我够不着在接下来的50多小时左右的情况下,我所有的交叉贴子走出去的手。

I think I need to mention Hopac in this context, as an interesting tangential and also mention I'm out of reach for the next 50 odd hours or so in case all my cross-postings go out of hand.

<编辑3 丹尼尔并的中使用自定义任务建设者的评论svick 提供良好的建议。丹尼尔提供了一个链接到一个已经在的 FSharpx

<edit 3: Daniel and svick give good advice in the comments to use a custom task builder. Daniel provides a link to a one that's already defined in FSharpx.

纵观我看到的参数接口被定义为

Looking at the the source I see the interface with the parameters are defined as

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

如果一个人在新奥尔良利用这一点,看起来像的TaskScheduler 应该是 TaskScheduler.Current 按文件的这里

If one were to use this in Orleans, it looks like the TaskScheduler ought to be TaskScheduler.Current as per documentation here

新奥尔良有自己的计划任务,提供晶粒内使用单线程
执行模型。重要的是,运行
任务时奥尔良调度使用,而不是.NET线程池。

Orleans has it's own task scheduler which provides the single threaded execution model used within grains. It's important that when running tasks the Orleans scheduler is used, and not the .NET thread pool.

如果您的谷物规范要求要创建一个子任务,你应该使用
Task.Factory.StartNew:

Should your grain code require a subtask to be created, you should use Task.Factory.StartNew:

等待Task.Factory.StartNew(()=> {/ *逻辑* /});

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

这技术将使用当前的任务调度,这将是
新奥尔良调度。

This technique will use the current task scheduler, which will be the Orleans scheduler.

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

You should avoid using Task.Run, which always uses the .NET thread pool, and therefore will not run in the single-threaded execution model.

它看起来有一个HREF之间<一个微妙的差异=htt​​p://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.current%28v=vs 0.110%29.aspx相对=nofollow> TaskScheduler.Current 和的 TaskScheduler.Default 。也许这是值得在其中的例子情况下就会有不期望的差异的问题。由于新奥尔良的文件指出,不使用 Task.Run ,而是引导到 Task.Factory.StartNew ,我不知道如果一个人应该定义 TaskCreationOptions.DenyAttachChild 作为由这类机构推荐的斯蒂芬Toub 是在的 Task.Run VS Task.Factory.StartNew 斯蒂芬·克利是在的 StartNew是危险的。嗯,它看起来像 .DEFAULT .DenyAttachChilld 除非我错了。

It looks there's a subtle difference between TaskScheduler.Current and TaskScheduler.Default. Maybe this warrants a question on in which example cases there'll be an undesired difference. As the Orleans documentation points out not to use Task.Run and instead guides to Task.Factory.StartNew, I wonder if one ought to define TaskCreationOptions.DenyAttachChild as is recommended by such authorities as Stephen Toub at Task.Run vs Task.Factory.StartNew and Stephen Cleary at StartNew is Dangerous. Hmm, it looks like the .Default will be .DenyAttachChilld unless I'm mistaken.

此外,由于没有与 Task.Run Task.Factory.CreateNew 关于自定义调度,不知这个特殊的问题可以通过使用自定义的 TaskFactory 中的任务计划程序(Task.Factory)和控制线程的数量并的如何:创建一个任务计划,限制并发

Moreover, as there is a problem with Task.Run viz Task.Factory.CreateNew regarding the custom scheduler, I wonder if this particular problem could be removed by using a custom TaskFactory as explained in Task Scheduler (Task.Factory) and controlling the number of threads and How to: Create a Task Scheduler That Limits Concurrency.

嗯,这是成为一个相当长的琢磨了。我不知道我应该怎么收呢?也许,如果 svick 丹尼尔可以使他们的意见作为答案,我会给予好评都接受的 svick的

Hmm, this is becoming quite a long "pondering" already. I wonder how should I close this? Maybe if svick and Daniel could make their comments as answers and I'd upvote both and accept svick's?

推荐答案

您可以使用FSharpx使用 TaskBuilder ,并通过在 TaskScheduler.Current 。这是我在翻译 RefreshHubs 的尝试。需要注意的是任务<部> 代替工作的使用

You can use use TaskBuilder in FSharpx and pass in TaskScheduler.Current. Here's my attempt at translating RefreshHubs. Note that Task<unit> is used in lieu of 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 ()
    }

这篇关于翻译异步-等待C#代码到F#相对于所述调度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆