Geeks With Blogs
George Mamaladze .NET C# tips, tricks, tweaks. Effective use of data structures and algorithms. Clean code.

I’m going to start with a simple code snippet which sorts an array of strings using LINQ.


1 IEnumerable<string> line = new[] {"Z","A","Ä"};
2 var result = line.OrderBy(letter => letter);
3 Console.WriteLine("{0}", string.Join(" ", result));


The result might look like this:




… or not. It depends on the thread culture the sorting is running in. The string order is culture aware (unlike char order which is culture invariant), so if we switch for instance on one of the Norwegian cultures by adding this line Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("nn-NO"); before calling sort, we will get following output instead:




As next I extended my code snippet to create 4 arrays and sort each of them parallely.


01 Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("nn-NO");
02 Console.WriteLine("Main thread-{0} \t Culture-'{1}'", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.CurrentCulture);
03 Console.WriteLine(new string('-', 80));
05 List<string[]> list = new List<string[]>();
06 for (int i = 0; i < 3; i++)
07 {
08     list.Add(new[] { "Ä", "A", "Z" });
09 }
11 var result =
12     list
13         .Select(
14             line => line
15                 .OrderBy(letter => letter));
17 Parallel.ForEach(result,
18     line =>
19         Console.WriteLine(
20             "Thread-{0} \t Culture-'{1}' \t {2}",
21             Thread.CurrentThread.ManagedThreadId,
22             Thread.CurrentThread.CurrentCulture,
23             string.Join(" ", line)));
25 Console.WriteLine();
26 Console.WriteLine("Press any key to quit");
27 Console.ReadKey();


The result looks like this:


Main thread-1    Culture-'nn-NO'
Thread-1         Culture-'nn-NO'         A Z Ä
Thread-5         Culture-'de-DE'         A Ä Z
Thread-3         Culture-'de-DE'         A Ä Z
Thread-4         Culture-'de-DE'         A Ä Z
Press any key to quit


Line 4 sorting order differs from line 5. The sorting was splited up into 4 threads one main and 3 new threads.

All three newly created threads got the default culture of my system – not the culture of the main thread which was set manually.

The culture is a property of the executing thread. When a thread is started, its culture is initially determined by using GetUserDefaultLCID from the Windows API. There is no way that I know manipulate this.

The same result if you use PLINQ syntax:


01 list
02     .AsParallel()
03     .Select(
04         line => line
05             .OrderBy(letter => letter))
06     .ForAll(
07         line =>
08             Console.WriteLine(
09                 "Thread-{0} \t Culture-'{1}' \t {2}",
10                 Thread.CurrentThread.ManagedThreadId,
11                 Thread.CurrentThread.CurrentCulture,
12                 string.Join(" ", line)));


The same query without parallel execution delivers consistent output, all four sequences are sorted in the same order.

The solution is to pass a specific culture aware comparer across into the OrderBy method.


01 var norvegianIgnoreCaseComparer = StringComparer.Create(CultureInfo.GetCultureInfo("nn-NO"),false);
02 list
03     .AsParallel()
04     .Select(
05         line => line
06             .OrderBy(letter => letter, norvegianIgnoreCaseComparer))
07     .ForAll(
08         line =>
09             Console.WriteLine(
10                 "Thread-{0} \t Culture-'{1}' \t {2}",
11                 Thread.CurrentThread.ManagedThreadId,
12                 Thread.CurrentThread.CurrentCulture,
13                 string.Join(" ", line)));


Well, but what about foreach and LINQ legacy code which can be paralelized with simple replacement of a single line by Parallel.ForEach() or adding AsParallel(). The result might be unpredictable and difficult to figure out. So if I would be the author of .NET or PLINQ I would take over the culture of the main thread into the child threads, thus the data come from the main thread, the split-up takes place implicitly and in most cases results are merged back into the main thread back and used there.

Similar issues might occur in queries using any of culture aware calculations, for instance DateTime formatting and parsing.

Posted on Tuesday, August 9, 2011 6:15 AM C# Language , .NET Framework , PLINQ | Back to top

Copyright © George Mamaladze | Powered by: