Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping a method with Task.Run( ... ) hangs when called in a Static constructor

Tags:

c#

task

freeze

I have a series of long-running functions. I want to wrap them in a Task so that I can run all of them concurrently, rather than waiting for each one to finish sequentially.

The method calls, and all relevant field values and methods and properties exist within a static class.

I'm having a problem wherein the static class constructor fails to complete because it is hanging when I wrap a method within with Task.Run.

In compliance with the requisite Mininmal, Complete and Verifiable example requirements...

using System;
using System.Linq;
using System.Threading.Tasks;

namespace MCVEAsyncHang
{
    class Program
    {
        private static readonly string[] _foo;

        static Program()
        {
            _foo = Task.WhenAll(Task.Run(new Func<string>(One))).Result;
        }

        private static string One()
        {
            return "Foo";
        }

        private static void Print()
        {
            Console.WriteLine(
                _foo.Aggregate((total, current) => 
                     total + string.Format("{0}{1}", Environment.NewLine, current)));
        }

        static void Main(string[] args)
        {
            Print();
            Console.WriteLine("Done");
            Console.ReadLine();
        }
    }
}

I understand I can just create some other method and call that ( and I will if I have to ( begrudgingly so ) ) but if it IS possible, I'd rather keep this within the static class constructor.

like image 252
Will Avatar asked Dec 23 '22 22:12

Will


1 Answers

Your task, which will be running in another thread, needs to call _one. That method can't execute until your Program type has been initialized.

The tasks's thread will see that the Program type is already being initialized in the main thread, and so will block until that thread has completed initializing the type. Unfortunately, that's not going to happen - because the type initializer is going to block until your task has finished. Deadlock.

Basically, you should avoid doing too much work in a static constructor. Launching tasks definitely feels like too much work. In this case the deadlock is obvious, but in other cases it could be much more subtle. (Before now I've spent hours debugging type initializer loops, and it's really, really not fun. That was single=threaded code - I dread to think how painful it would be in a multi-threaded environment.)

like image 190
Jon Skeet Avatar answered May 09 '23 06:05

Jon Skeet