Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/259.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用控制台和PLinq时出现明显死锁_C#_.net_Parallel.foreach_Plinq - Fatal编程技术网

C# 使用控制台和PLinq时出现明显死锁

C# 使用控制台和PLinq时出现明显死锁,c#,.net,parallel.foreach,plinq,C#,.net,Parallel.foreach,Plinq,以下代码运行时没有问题: // This code outputs: // 3 // 2 // 1 // // foo // DotNetFiddle: https://dotnetfiddle.net/wDRD9L public class Program { public static void Main() { Console.WriteLine("foo"); } static Program() {

以下代码运行时没有问题:

// 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());
    }
}
起初我怀疑Console.WriteLine不是线程安全的,但根据文档,它是线程安全的


这种行为的解释是什么?

简短版本:永远不要在构造函数内部阻塞,尤其是在
静态
构造函数中

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

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

僵局


很难知道什么是好的变通方案,因为该示例(正如预期的)是您真正正在做的任何事情的简化版本。但底线是,您不应该在静态构造函数中进行长时间运行的计算。如果它是一个足够慢的算法,可以证明使用
ForAll()
,那么它的速度就足够慢,一开始就不应该是类初始化的一部分

在解决这个问题的许多可能的选项中,您可以选择
Lazy
类,它可以轻松地将一些初始化延迟到实际需要时

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

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;
    }
}
公共类程序
{   
公共静态void Main()
{
Console.WriteLine(“foo”);
}
private static readonly Lazy _list=new Lazy(()=>InitList());
私有静态列表InitList()
{
var list=new list(){1,2,3};
list.aspallel().WithDegreeOfParallelism(4).ForAll(item=>{Console.WriteLine(item.ToString());});
退货清单;
}
}
然后,初始化代码甚至不会执行,直到一些代码需要访问列表,它可以通过
\u list.Value
访问列表


这是非常微妙的不同,以至于我觉得应该有一个新的答案(即匿名方法的使用改变了行为),但至少还有两个与堆栈溢出密切相关的问题和答案:



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

这可能与此有关:-您正在调用Console.WriteLine()在后台线程中,在它被正确初始化之前。你很幸运地使用了
sb.AppendLine
,该方法不是线程安全的,如果你有越来越多的字符串,你的字符串可能会格式不正确,甚至会丢失一些。对我来说,即使没有
控制台。WriteLine
内部
程序的静态构造函数。Ca在cctor中调用
ForAll
(主体为空)会触发挂起。(针对4.5.2,VS 2013)我只是在这里猜测,但我认为当调用并行循环时,
程序的静态构造函数没有完成,底层代码进入等待状态,等待静态构造函数完成类型的反斜体化(从线程开始的位置)。如果您将逻辑移动到单独的静态函数,它将在不挂起的情况下工作。这可能是重复的:“Console.WriteLine与此无关。谢谢,这很好地解释了这一点。仍然可以很好地指出,静态方法调用中的阻塞只发生在不同线程之间。从静态构造函数正在运行的同一线程中调用的静态方法可以被很好地调用。尽管如此,这可能不是一个好的做法。“静态方法调用中的阻塞只发生在不同的线程之间”——是的,这是正确的。“这可能不是一个好的做法。”"--不,我认为这很好,只要不违反其他一些良好实践wrt静态构造函数。例如,假设初始化适用于静态构造函数,但足够冗长,将其分解为一个或多个方法可以改进代码,那么这样做是一件好事,而不是不可避免的事情。我将把问题编辑到cla验证关于跨线程调用的要点。
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;
    }
}