2

我正在编写一个使用FSharp.Collections.ParallelSeq重试计算的刮板。我想从多个页面并行检索 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

GetHtmlForAllPages当我尝试从代码进行交互时main似乎挂起。单步执行代码向我展示了PSeq.map开始处理pages.

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

如果我创建GetHtmlForAllPages一个函数并调用它,代码会按预期工作。我很好奇什么时候GetHtmlForAllPages是一个领域?

4

1 回答 1

5

看起来你在静态构造函数中死锁了。此处描述了该场景:

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为函数(通过添加()),它就不再是该静态构造函数的一部分,这使得程序按预期工作。

于 2017-03-29T08:22:44.423 回答