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?
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.
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();
}
}
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With