gpt4 book ai didi

c# - 使用 Console 和 PLinq 的明显死锁

转载 作者:太空宇宙 更新时间:2023-11-03 12:56:08 25 4
gpt4 key购买 nike

以下代码运行没有问题:

// This code outputs:
// 3
// 2
// 1
//
// foo
// DotNetFiddle: https://dotnetfiddle.net/wDRD9L
public class Program
{
public static void Main()
{
Console.WriteLine("foo");
}

static Program()
{
var sb = new System.Text.StringBuilder();
var list = new List<int>() { 1,2,3 };
list.AsParallel().WithDegreeOfParallelism(4).ForAll(item => { sb.AppendLine(item.ToString()); });
Console.WriteLine(sb.ToString());
}
}

一旦我将 sb.AppendLine 替换为对 Console.WriteLine 的调用,代码就会挂起,就像某处出现死锁一样。

// This code hangs.
// DotNetFiddle: https://dotnetfiddle.net/pbhNR2
public class Program
{
public static void Main()
{
Console.WriteLine("foo");
}

static Program()
{
var list = new List<int>() { 1,2,3 };
list.AsParallel().WithDegreeOfParallelism(4).ForAll(item => { Console.WriteLine(item.ToString()); });
}
}

起初我怀疑 Console.WriteLine 不是线程安全的,但根据文档,它是线程安全的。

这种行为的解释是什么?

最佳答案

简短版本:永远不要在构造函数中阻塞,尤其是在 static 中。构造函数。

在您的示例中,差异与您使用的匿名方法有关。在第一种情况下,您捕获了一个局部变量,它导致匿名方法被编译到它自己的类中。但在第二种情况下,没有变量捕获,所以 static方法就够了。除了将静态方法放入 Program 之外类(class)。仍在初始化中。

因此,对匿名方法的调用被类的初始化阻塞(您不能从执行静态构造函数的线程以外的线程执行类中的方法,直到该类完成初始化),并且类的初始化被匿名方法的执行阻塞(ForAll() 方法在所有这些方法都执行之前不会返回)。

死锁。


鉴于该示例(正如预期的那样)是您实际操作的简化版本,因此很难知道什么是好的变通方法。但最重要的是,您不应该在静态构造函数中进行长时间运行的计算。如果它是一个足够慢的算法,它证明使用 ForAll() 是合理的。 , 那么它就足够慢了,以至于它实际上不应该首先成为类初始化的一部分。

在解决该问题的许多可能选项中,您可能会选择一个 Lazy<T>类,这样可以很容易地将某些初始化推迟到实际需要时进行。

例如,假设您的并行代码不只是写出列表的元素,而是以某种方式实际处理它们。 IE。它是列表实际初始化的一部分。然后您可以将该初始化包装在由 Lazy<T> 执行的工厂方法中按需而不是在静态构造函数中:

public class Program
{
public static void Main()
{
Console.WriteLine("foo");
}

private static readonly Lazy<List<int>> _list = new Lazy<List<int>>(() => InitList());

private static List<int> InitList()
{
var list = new List<int>() { 1,2,3 };
list.AsParallel().WithDegreeOfParallelism(4).ForAll(item => { Console.WriteLine(item.ToString()); });

return list;
}
}

然后初始化代码根本不会被执行,直到一些代码需要访问列表,它可以通过_list.Value来完成。 .


这是非常微妙的不同,我觉得它需要一个新的答案(即匿名方法的使用改变了行为),但在 Stack Overflow 上至少有两个其他非常密切相关的问题和答案:
Plinq statement gets deadlocked inside static constructor
Task.Run in Static Initializer


顺便说一句:我最近了解到,使用新的 Roslyn 编译器,他们改变了在这种情况下实现匿名方法的方式,甚至那些可能是静态方法的方法都在单独的类中创建了实例方法(如果我没记错的话)。我不知道这是否是为了减少这种错误的流行,但它肯定会改变行为(并且会消除匿名方法作为死锁的来源......当然,人们总是可以重现问题对显式声明的静态命名方法的调用)。

关于c# - 使用 Console 和 PLinq 的明显死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33789721/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com