为什么带有计算表达式的 PSeq.map 似乎挂起了?



我正在使用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

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

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

如果我GetHtmlForAllPages函数并调用它,代码将按预期工作。我很好奇当GetHtmlForAllPages是一个领域时发生了什么?

看起来您在静态构造函数中死锁。下面描述了该方案:

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

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

通过这种行为 CLR,如果我们执行任何 静态构造函数中的异步阻塞操作。(...)

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

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

最新更新