Plinq statement gets deadlocked inside static cons

2020-02-09 11:43发布

I came across this situation where the following plinq statement inside static constructor gets deadlocked:

static void Main(string[] args)
{
    new Blah();
}

class Blah
{
     static Blah()
     {
         Enumerable.Range(1, 10000)
            .AsParallel()
            .Select(n => n * 3)
            .ToList();
     }
}

It happens only when a constructor is static. Can someone explain this to me please.

Is it TPL bug? Compiler? Me?

3条回答
冷血范
2楼-- · 2020-02-09 11:50

While the reason has already been explained as to why you wouldn't want to do threaded work inside a static constructor, I thought I'd add that the "right" way to do this instead would be with a static Lazy<T>. This is also more efficient as the work to generate those resources will be defferred until those resources are actually needed.

class Blah
{
    // Supply factory method to generate the numbers, but actual generation will be deferred
    private static Lazy<List<int>> MyMagicNumbers = new Lazy<List<int>>(Blah.GenerateMagicNumbers);

    public void DoSomethingWithMagicNumbers()
    {
        // Call to Lazy<T>.Value will synchronize any calling threads until value is initially generated from the factory
        List<int> magicNumbers = Blah.MyMagicNumbers.Value;

        // ... do something here ...
    }

    private List<int> GenerateMagicNumbers()
    {
        return Enumerable.Range(1, 10000)
                          .AsParallel()
                          .Select(n => n * 3)
                          .ToList();
    }
}
查看更多
▲ chillily
3楼-- · 2020-02-09 11:57

For what its worth, the issue does not arise on Mono:

[mono] /tmp @ dmcs par.cs 
[mono] /tmp @ mono ./par.exe 

Do you have a windows compiled binary so I can compare the generated MSIL? I'm not convinced this is a library-only issue, and I'm curious :)


Comparing the IL was a bit messy, so I decided to just try both binaries on both platforms. Hehe I revived my old Windows virtual machine just to test this :)

Running the VS compiled binaries on Mono is no problem. You could try it on windows using 2.10.1 (http://www.go-mono.com/mono-downloads/download.html), only 77.4Mb :)

(I used a custom built mono 2.11 on linux so it could be that the feature support is not complete yet)

     \ run on platform:      MS.Net 4.0      Mono 2.1x
built on: -------------+----------------------------------------
    Visual Studio       |      deadlock       no deadlock
                        |
    MonoDevelop         |      deadlock       no deadlock

I also noticed that when running on windows, a CTRL-C is able to break out of the lock. Will post if I find some more to this.


Update 2

Well, installing Mono runs circles around installing installing VSExpress even on windows. Installing mono has finished in 4 minutes, and resulted in:

C:\Users\Seth>"c:\Program Files (x86)\Mono-2.10.1\bin\mono.exe" ConsoleApplication2.exe
C:\Users\Seth>

No deadlock :) Now all that remains is waiting for VSExpress to be installed (forever) and istall debugging tools (unknown) and than have a crack at it (probably till late night). CU later

查看更多
男人必须洒脱
4楼-- · 2020-02-09 12:06

It is generally dangerous to call threading code from a static constructor. In order to ensure that the static constructor executes only once, the CLR executes the static constructor under a lock. If the thread running the static constructor waits on a helper thread, there is a risk that the helper thread is going to need the CLR-internal lock for some reason too, and the program will deadlock.

Here is a simpler code sample that demonstrates the problem:

using System.Threading;
class Blah
{
    static void Main() { /* Won’t run because the static constructor deadlocks. */ }

    static Blah()
    {
        Thread thread = new Thread(ThreadBody);
        thread.Start();
        thread.Join();
    }

    static void ThreadBody() { }
}

Section 10.5.3.3 "Races and deadlocks" of the ECMA CLI spec guarantees the following:

Type initialization alone shall not create a deadlock unless some code called from a type initializer (directly or indirectly) explicitly invokes blocking operations.

So, a type initializer (i.e., a static constructor) will not deadlock, provided that no operation in the static constructor blocks the thread. If the static constructor does block, it risks a deadlock.

查看更多
登录 后发表回答