6

I cannot understand why the following code will not work:

var task = new Task(() => { });
task.Start();
if (task.Wait(10000))
{
   logger.Info("Works");
}
else
{  
   logger.Info("Doesn't work");
}

The task status is stuck on "Running" after the timeout expires, although there is nothing to be done. Replacing task.Start() with task.RunSynchronously() will work however.

Does anyone have an idea of what I may be doing wrong?

A test project to replicate the issue is available here: http://erwinmayer.com/dl/TaskTestProject.zip. As far as I can see, it doesn't work if the method with the above code runs within the static constructor. But it works if called directly as a static class method.

This recent MSDN blog post seems to highlight related issues with static constructors: http://blogs.msdn.com/b/pfxteam/archive/2011/05/03/10159682.aspx

Erwin Mayer
  • 18,076
  • 9
  • 88
  • 126

3 Answers3

10

The context is very important here. When you start a task like this, it uses the current scheduler - and if that assumes it will be able to use the current thread, you'll effectively deadlock when you wait for it.

The same code in different context would be fine.

The reason others are saying it's working for them, but it's not working for you, is that no doubt you're running this code in a different context to other people - but you haven't shown us a short but complete program, just this snippet, so everyone is trying to reproduce it in a different way. (I see you've now uploaded a project, which will no doubt shed more light. A short but complete program which could be posted in the question is generally preferable, of course.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 3
    More than 1,000,000 bugs died ,the day Jon Skeet was born and they are still dying ... – Surjit Samra Dec 21 '11 at 19:20
  • Jon, either I misunderstand you, or your brain has farted. Likely the former, but your first paragraph reads wrong to me. Granted I have limited experience, but I don't know of any situation where what you said makes sense. In winforms when you start a task using the default scheduler, it certainly doesn't run on the UI thread. – Igby Largeman Dec 21 '11 at 19:48
  • Were you implying that his program has must have explicitly changed the default scheduler to one which queues the task to the current thread? If you were, it would make sense, assuming that the scheduler in question simply queues the task but never runs it. – Igby Largeman Dec 21 '11 at 19:59
  • @Charles: My mistake no doubt. I strongly suspect that the thrust of the answer is correct, but the details are wrong :) Can't test right now, but will edit to be more general. – Jon Skeet Dec 21 '11 at 20:03
  • I suspect the same... the notion of it being a scheduler problem makes sense. – Igby Largeman Dec 21 '11 at 20:08
  • Technically it's not a deadlock. The task is blocked but the program continues. – Igby Largeman Dec 21 '11 at 20:43
  • @Charles: Well if the task is blocked on the waiting thread (because it can't start) and the waiting thread is blocked on the task, it sounds like a deadlock to me - not in the classic two-threads-each-with-a-mutual-lock way, but in the "two actions which each require the other to complete before they can make progress" sense. – Jon Skeet Dec 21 '11 at 21:43
  • The waiting thread only waits until its timeout is reached, and then continues. It isn't blocked. (At least not by my definition... perhaps my definition is wrong.) – Igby Largeman Dec 21 '11 at 21:46
  • @Charles: Yes, it's a temporary deadlock due to the timeout - but *while it's waiting* it's deadlocked. A deadlock with a timeout is still logically a deadlock IMO :) – Jon Skeet Dec 21 '11 at 22:28
  • 1
    Based on the follow-up below it looks like the original poster fell victim to the same bug that Neal and I described in our puzzlers talk in Norway. Multithreading and static ctors do not mix! – Eric Lippert Dec 21 '11 at 23:43
  • 2
    @Eric: I'd say that "static ctors and blocking don't mix". I tend to use static constructors precisely *for* their threading behaviour, in terms of guaranteeing exactly-once initialization. But yes, clearly the static constructor bit was what we were missing earlier. – Jon Skeet Dec 22 '11 at 08:04
1

Thank you everyone for your comments, it helped me be more specific and finally isolate the issue.

I created a test project here: http://erwinmayer.com/dl/TaskTestProject.zip. It shows that the code in my question doesn't work if it runs within the static constructor. But it does work if called directly as a static class method, after the static constructor has been initialized.

This recent MSDN blog post provides some technical insight on the existence of related issues when dealing with multithreading and static constructors:

Erwin Mayer
  • 18,076
  • 9
  • 88
  • 126
  • Are you now happy enough with what's going on that it's not worth me going into details in my answer? (If so, I might as well delete it.) Otherwise, I could go through step-by-step what's happening and why it's causing a deadlock, if you'd like. – Jon Skeet Dec 22 '11 at 08:05
  • I grasped the general idea; now if you know exactly how the CLR creates locks while using multi-threading in the static constructor I believe it can be very insightful for people who will read this thread. – Erwin Mayer Dec 22 '11 at 10:25
  • I don't know the implementation details, just what's guaranteed in terms of observed behaviour. – Jon Skeet Dec 22 '11 at 10:27
0

It works:

        var task = new Task(() => { });
        task.Start();
        if (task.Wait(10000))
        {
            Console.WriteLine("yes");
        }
        else
        {
            Console.WriteLine("no");
        }

And gives the output yes as expected. You must be doing something else which is causing it to not work. In the given form, without context of what / where you are doing it, it works.

Even this abomination works:

        var task = new Task(() =>
                                {

                                    var task1 = new Task(() =>
                                                            {
                                                            });
                                    task1.Start();
                                    if (task1.Wait(10000))
                                    {
                                        Console.WriteLine("yes");
                                    }
                                    else
                                    {
                                        Console.WriteLine("no");
                                    }

                                });
        task.Start();
        if (task.Wait(10000))
        {
            Console.WriteLine("yes");
        }
        else
        {
            Console.WriteLine("no");
        }
manojlds
  • 290,304
  • 63
  • 469
  • 417