草庐IT

c# - 根据调度程序将 async-await C# 代码转换为 F#

coder 2024-05-22 原文

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

示例代码太长,这里无法重现,但有趣的功能是 ActivateAsync , RefreshHubsAddHub .特别有趣的地方是

  • AddHub签名为 private async Task AddHub(string address) .
  • RefreshHubs电话AddHub在循环中收集 tasks 的列表,然后它在最后等待 await Task.WhenAll(tasks)因此返回值匹配其签名 private async Task RefreshHubs(object _) .
  • RefreshHubsActivateAsync 调用正如 await RefreshHubs(null)然后最后有一个电话await base.ActivateAsync()匹配函数签名 public override async Task ActivateAsync() .

  • 问题:

    将此类函数签名正确转换为 F# 的正确转换是什么,该 F# 仍然保持界面和功能并尊重默认的自定义调度程序?而且我也不太确定这种“F# 中的异步/等待”。至于如何“机械地”做到这一点。 :)

    原因是在链接“here (1)”中似乎存在问题(我尚未验证这一点),因为 F# 异步操作不尊重(奥尔良)运行时设置的自定义协作调度程序。另外,它说 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 .另一个有趣的选项可能是 here作为
    module Async =
        let AwaitTaskVoid : (Task -> Async<unit>) =
            Async.AwaitIAsyncResult >> Async.Ignore
    

    <>我刚刚注意到 Task.WhenAll也需要等待。但正确的方法是什么?呃,该 sleep 了(一个糟糕的双关语)......

    <>here (1) (TPL-F# 交互的原始问题)在 Codeplex 中提到 F# 使用同步上下文将工作推送到线程,而 TPL 没有。现在,这是一个合理的解释,我觉得(尽管无论自定义调度程序如何,我仍然在正确翻译这些片段时遇到问题)。一些有趣的附加信息可以从
  • How to get a Task that uses SynchronizationContext? And how are SynchronizationContext used anyway?
  • Await, SynchronizationContext, and Console Apps其中一个例子SingleThreadSynchronizationContext提供看起来像排队要执行的工作。也许这应该被使用?

  • 我想我需要提到Hopac在这种情况下,作为一个有趣的切线,还提到我在接下来的 50 多个小时左右无法联系,以防我所有的交叉发布失控。

    <编辑 3=""> :Danielsvick在评论中给出很好的建议以使用自定义任务构建器。 Daniel 提供了一个链接,该链接已在 FSharpx 中定义。 .

    查看源代码我看到带有参数的接口(interface)定义为
    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根据文档 here

    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.

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

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

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

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



    看起来 TaskScheduler.Current 之间有细微的差别和 TaskScheduler.Default .也许这需要提出一个问题,即在哪些示例情况下会出现不希望有的差异。正如奥尔良文档指出的那样,不要使用 Task.Run而是指向 Task.Factory.StartNew , 我想知道是否应该定义 TaskCreationOptions.DenyAttachChild正如 等权威机构所推荐的那样斯蒂芬·图布 Task.Run vs Task.Factory.StartNew斯蒂芬·克利里 StartNew is Dangerous .嗯,看起来像 .Default将是 .DenyAttachChilld除非我弄错了。

    而且,由于Task.Run有问题即Task.Factory.CreateNew关于自定义调度程序,我想知道是否可以通过使用自定义 TaskFactory 来消除这个特定问题。如 Task Scheduler (Task.Factory) and controlling the number of threads 中所述和 How to: Create a Task Scheduler That Limits Concurrency .

    嗯,这已经变成了一个相当长的“思考”。我想知道我应该如何关闭这个?也许如果 斯维克丹尼尔可以将他们的评论作为答案,我会赞成并接受 斯维克的 ?

    最佳答案

    您可以使用 TaskBuilder在 FSharpx 中并传入 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 ()
        }
    

    关于c# - 根据调度程序将 async-await C# 代码转换为 F#,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24813359/

    有关c# - 根据调度程序将 async-await C# 代码转换为 F#的更多相关文章

    1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    2. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

      我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

    3. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    4. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

      如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

    5. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    6. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

      在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

    7. ruby - 将数组的内容转换为 int - 2

      我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

    8. ruby - 将散列转换为嵌套散列 - 2

      这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

    9. ruby - 在 Ruby 中编写命令行实用程序 - 2

      我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

    10. ruby-on-rails - Rails 应用程序之间的通信 - 2

      我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

    随机推荐