Why is Task.WhenAll () blocking here?

I have a pretty simple piece of code, but it seems to block on Task.WhenAll (). The main function just calls new Test()

. Can anyone help find the root cause?

internal class Test
{
    public static async Task<int> Foo()
    {
        return 0;
    }

    static Test()
    {
        var taskList = new List<Task>();

        for (int i = 0; i < 100; i++)
            taskList.Add(Task.Run(() => Foo()));

        Task.WhenAll(taskList.ToArray()).GetAwaiter().GetResult();
    }
}

      

+3


source to share


2 answers


The CLR takes global blocking when the static constructor runs. The static constructor must release this lock before other threads can inject any method into the class. This is to ensure that the static constructor is run and completed only once for each application. Here is the same problem when streaming was more explicit.

class Test
{
    static Test() {
       var thread = new Thread(ThreadMethod);
       thread.Start();
       thread.Join();
    }

    private static void ThreadMethod()
    {
    }
}

      



The new thread cannot enter ThreadMethod

until the static constructor exits, but the static constructor cannot exit until the thread completes, deadlock.

Your example is a more complex version that uses Task.Run

instead of creating Thread

and runs it and then GetResult

blocks the same as Thread.Join

.

+9


source


You are at a dead end. Note that your code is in a static constructor for your class. This means that initialization of a different type cannot happen until your type initializer has completed. But your type initializer won't be finished until all of these tasks have finished. But if these tasks rely on some other type that hasn't been initialized yet, they cannot finish.

The solution is to implement this initialization, which you are trying to do with some other mechanism. Run tasks in the static constructor if you like, but fix your code so your instances can wait for the result outside of the static constructor. For example, instead of using a static constructor, use Lazy<T>

:



private static readonly Lazy<int> _tasksResult = new Lazy<int>(
    () => InitTest());

static int InitTest()
{
    var taskList = new List<Task>();

    for (int i = 0; i < 100; i++)
        taskList.Add(Task.Run(() => Foo()));

    return Task.WhenAll(taskList.ToArray()).GetAwaiter().GetResult();
}

      

Or something like that (it's not entirely clear what result you are really interested in here, so there is a bit of manual swing in it above).

+3


source







All Articles