【问题标题】:Why does PSeq.map with computation expression seem to hang?为什么带有计算表达式的 PSeq.map 似乎挂起?
【发布时间】:2017-03-29 16:29:27
【问题描述】:

我正在使用FSharp.Collections.ParallelSeqretry computation 编写刮板。我想从多个页面并行检索 HTML,并且我想在请求失败时重试请求。

例如:

open System
open FSharp.Collections.ParallelSeq

type RetryBuilder(max) = 
  member x.Return(a) = a               // Enable 'return'
  member x.Delay(f) = f                // Gets wrapped body and returns it (as it is)
                                       // so that the body is passed to 'Run'
  member x.Zero() = failwith "Zero"    // Support if .. then 
  member x.Run(f) =                    // Gets function created by 'Delay'
    let rec loop(n) = 
      if n = 0 then failwith "Failed"  // Number of retries exceeded
      else try f() with _ -> loop(n-1)
    loop max

let retry = RetryBuilder(4)

let getHtml (url : string) = retry { 
    Console.WriteLine("Get Url")
    return 0;
}

//A property/field?
let GetHtmlForAllPages = 
    let pages = {1 .. 10}
    let allHtml = pages |> PSeq.map(fun x -> getHtml("http://somesite.com/" + x.ToString())) |> Seq.toArray
    allHtml

[<EntryPoint>]
let main argv = 
    let htmlForAllPages = GetHtmlForAllPages
    0 // return an integer exit code

当我尝试从mainGetHtmlForAllPages 交互时,代码似乎挂起。单步执行代码显示PSeq.map 开始处理pages 的前四个值。

发生了什么导致retry 计算表达式永远无法启动/完成? PSeqretry 之间有什么奇怪的相互作用吗?

如果我将GetHtmlForAllPages 设为函数并调用它,代码将按预期工作。我很好奇GetHtmlForAllPages 是一个字段时会发生什么?

【问题讨论】:

    标签: f# computation-expression


    【解决方案1】:

    看起来您在静态构造函数中陷入僵局。场景描述here

    CLR 使用内部锁来确保静态构造函数:

    • 只调用一次
    • 在创建任何实例之前执行 类或访问任何静态成员之前。

    有了这种行为 CLR,如果我们执行任何操作,可能会出现死锁 静态构造函数中的异步阻塞操作。 (...)

    主线程将等待辅助线程在 静态构造函数。由于辅助线程正在访问实例 方法,它会首先尝试获取内部锁。作为内部 锁已经被主线程获取,我们将在一个 死锁情况。

    在静态构造函数中使用 Parallel LINQ(或任何其他类似的库,如 FSharp.Collections.ParallelSeq)会让您遇到这个问题。

    不幸的是,编译器生成的类的静态构造函数是您的GetHtmlForAllPages 值。来自 ILSpy(使用 C# 格式):

    namespace <StartupCode$ConsoleApplication1>
    {
        internal static class $Program
        {
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            internal static readonly Program.RetryBuilder retry@17;
    
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            internal static readonly int[] GetHtmlForAllPages@24;
    
            [DebuggerBrowsable(DebuggerBrowsableState.Never), DebuggerNonUserCode, CompilerGenerated]
            internal static int init@;
    
            static $Program()
            {
                $Program.retry@17 = new Program.RetryBuilder(4);
                IEnumerable<int> pages = Operators.OperatorIntrinsics.RangeInt32(1, 1, 10);
                ParallelQuery<int> parallelQuery = PSeqModule.map<int, int>(new Program.allHtml@26(), pages);
                ParallelQuery<int> parallelQuery2 = parallelQuery;
                int[] allHtml = SeqModule.ToArray<int>((IEnumerable<int>)parallelQuery2);
                $Program.GetHtmlForAllPages@24 = allHtml;
            }
        }
    }
    

    在你实际的Program 类中:

    [CompilationMapping(SourceConstructFlags.Value)]
    public static int[] GetHtmlForAllPages
    {
        get
        {
            return $Program.GetHtmlForAllPages@24;
        }
    }
    

    这就是死锁的来源。

    一旦您将GetHtmlForAllPages 更改为函数(通过添加()),它就不再是该静态构造函数的一部分,这将使程序按预期工作。

    【讨论】:

      猜你喜欢
      • 2022-11-03
      • 2015-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多