4

I have an application, which has run without issue for a long time, which suddenly fails to start due to the following error:

"This property has already been set and cannot be modified."

When I inspect the code, which basically resembles the snippet below, I see the exception is thrown on the line which attempts to name the first task inside Parallel.Invoke

Thread.CurrentThread.Name = "Main Program Thread";

// Do some start up tasks in parallel
Parallel.Invoke(new ParallelOptions { MaxDegreeOfParallelism = 10 },
() =>
{
    Thread.CurrentThread.Name = "First thread";
},
() =>
{
    Thread.CurrentThread.Name = "Second thread";
});
                        ...

Obviously the cause of this must be that the main thread already has a name, and the first task is being run on the main thread rather than a threadpool thread.

Whilst I can resolve this by not naming the threads inside Parallel.Invoke, I am curious as to why this has suddenly started happening. Is it the case that normally Parallel.Invoke() previously ran all its tasks on threadpool threads and for some reason is unable to do so anymore? What could trigger this sort of thing?

The more I look at this code the more perplexed I am that it has ever worked. It looks to me like this code should always throw an exception.

JMc
  • 971
  • 2
  • 17
  • 26
  • 1
    Did you execute the above code in a `Task`? If so there is no guarantee that `Parallel.Invoke` will execute the actions in a different thread. – Old Fox Jul 16 '15 at 11:24
  • Parallel.Invoke is called from the main thread as part of the application start up. Inside Parallel.Invoke are a number of tasks which perform operations required for application initialisation (reading caches from the database etc) – JMc Jul 16 '15 at 13:16
  • You probably installed .net 4.5 where they "fixed" Parallel Extensions. – Aron Jul 16 '15 at 14:19
  • @JMc take a look in the [source code](https://github.com/dotnet/corefx/blob/master/src/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs) lines 337-363. In line 357 you call `Wait` on the tasks. According to [this wiki answer](http://stackoverflow.com/questions/12245935/is-task-factory-startnew-guaranteed-to-use-another-thread-than-the-calling-thr), when you call `wait` on a Task in the `WaitingToRun` state could cause your situation. – Old Fox Jul 16 '15 at 14:26
  • I just recalled that I downloaded VS 2015 RC during the week, although Im not building or running this app through that version of VS. Any thoughts on whether that is a potential cause? It has installed .Net Framework v4.6 on my machine.... – JMc Jul 16 '15 at 15:16

3 Answers3

1

Parallel usually runs some work on the thread-pool and some on the current thread in order to not let it sit idle. This is not guaranteed, all work can run on the pool or on the current thread.

I don't know what happens when you assign a name to a pool thread. Either it throws, does nothing or works once. None of that is desirable.

Throw this code away. Don't mess with threads you don't own.

You can use the LongRunning task option to get dedicated threads that you can configure.

usr
  • 168,620
  • 35
  • 240
  • 369
0

Thread-pool threads are reused. The thread a threadpool task runs on does not belong to you. What you're doing is explicitly forbidden, so do not be surprised when it explodes in your face.

Apart from that, Parallel.Invoke is perfectly within it's reasonable operating parameters when it runs some of its code on the same thread it's called from - there's no point in just waiting for all the other threads to finish, and wasting a perfectly fine thread.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • 1
    "What you're doing is explicitly forbidden" - Where is this explicitly forbidden? Also, I didn't write this code, I just maintain it, but that's not my issue. I have run the code hundreds of times without ever seeing this exception. The code runs in production daily for the past two years without ever seeing this exception, my question is what changes could happen to trigger the behaviour I see now? Is it pure dumb luck that this application (two separate instances of it actually) has run for two years without encountering this exception? – JMc Jul 16 '15 at 13:11
  • 1
    @JMc It is implicitly forbidden given the name "ThreadPool". For example, you do not take the public bus off to be part exchanged for your next car, because you do not own it. Also "I only maintain it" does not mean you should not fix it. Fact is the code is very very wrong. – Aron Jul 16 '15 at 13:54
  • I agree, I should and will fix it. You've not answered my question though. How did it ever work and why does it consistently fail now? – JMc Jul 16 '15 at 14:20
  • @JMc It's hard to tell. It might be that it's a recent performance optimization (it wouldn't be considered a breaking change, since you're doing unsupported stuff). It might be that something tiny changed which means that initializing the task now takes slightly longer (or shorter), so that there's now enough time to try and run the task inline. It might be that you used to execute more tasks at the same time - the inlining only happens if there's at most four tasks / actions to invoke. The behaviour is also different for more than ten tasks. Try to avoid undefined behaviour in the future :) – Luaan Jul 16 '15 at 14:35
  • Thanks Luaan - I've run this code on multiple other machines here without issue. The problem only manifests on my own machine. I recently changed my power settings from balanced to high performance. Could this be a potential cause? Unfortunately reverting back to balanced power hasn't corrected the problem though. – JMc Jul 16 '15 at 15:08
  • @JMc It's far more likely that you have a different version of the .NET framework. Try checking the exact version number on clr.dll, for example. – Luaan Jul 16 '15 at 18:14
0

I uninstalled .NET 4.6 and this code began to work again. There must be an optimisation in Parallel.Invoke in newer versions of the framework which exposed the bug in our code where it never manifest before.

JMc
  • 971
  • 2
  • 17
  • 26