Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ Parallelization sometimes causes a PLINQ query to run slower than its LINQ to Ob

In sequential code, it is not uncommon to read from or write to static variables or class fields. However, whenever multiple threads are accessing such variables concurrently, there is a big potential for race conditions. Even though you can use locks to synchronize access to the variable, the cost of synchronization can hurt performance. Therefore, we recommend that you avoid, or at least limit, access to shared state in a PLINQ query as much as possible.

### Example: Race condition with shared memory

The following example demonstrates a race condition that occurs when multiple threads write to a shared variable. The variable `total` is accessed concurrently by multiple threads without synchronization, leading to unpredictable results:

:::code language="csharp" source="./snippets/potential-pitfalls-with-plinq/csharp/RaceConditionExample/Program.cs" id="RaceConditionBad":::
:::code language="vb" source="./snippets/potential-pitfalls-with-plinq/vb/RaceConditionExample/Program.vb" id="RaceConditionBad":::

In this code, the operation `total += n` is not atomic. It involves reading the current value, adding `n`, and writing the result back. When multiple threads execute this operation simultaneously, they can read the same value, add to it, and write back results that overwrite each other. This causes some additions to be lost, producing an incorrect final result.

The correct approach is to use thread-safe operations that don't require shared mutable state:

:::code language="csharp" source="./snippets/potential-pitfalls-with-plinq/csharp/RaceConditionExample/Program.cs" id="RaceConditionGood":::
:::code language="vb" source="./snippets/potential-pitfalls-with-plinq/vb/RaceConditionExample/Program.vb" id="RaceConditionGood":::

The `Sum` method handles parallelization internally in a thread-safe manner, ensuring correct results without the need for explicit synchronization. Other safe approaches include using <xref:System.Linq.ParallelEnumerable.Aggregate%2A> for custom aggregations or collecting results into thread-safe collections like <xref:System.Collections.Concurrent.ConcurrentBag%601>.

## Avoid over-parallelization

By using the `AsParallel` method, you incur the overhead costs of partitioning the source collection and synchronizing the worker threads. The benefits of parallelization are further limited by the number of processors on the computer. There is no speedup to be gained by running multiple compute-bound threads on just one processor. Therefore, you must be careful not to over-parallelize a query.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Linq;

class RaceConditionExample
{
static void Main()
{
Console.WriteLine("=== Race Condition Example ===");
DemonstrateRaceCondition();

Console.WriteLine("\n=== Fixed Version ===");
DemonstrateCorrectApproach();
}

// <RaceConditionBad>
static void DemonstrateRaceCondition()
{
int total = 0;
var numbers = Enumerable.Range(0, 10000);

// UNSAFE: Multiple threads writing to shared variable
numbers.AsParallel().ForAll(n => total += n);

Console.WriteLine($"Total (with race condition): {total}");
// Expected: 49,995,000 but result is unpredictable due to race condition
}
// </RaceConditionBad>

// <RaceConditionGood>
static void DemonstrateCorrectApproach()
{
var numbers = Enumerable.Range(0, 10000);

// SAFE: Use thread-safe aggregate operation
int total = numbers.AsParallel().Sum();

Console.WriteLine($"Total (correct): {total}");
// Result is always 49,995,000
}
// </RaceConditionGood>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Imports System
Imports System.Linq

Class RaceConditionExample
Shared Sub Main()
Console.WriteLine("=== Race Condition Example ===")
DemonstrateRaceCondition()

Console.WriteLine(Environment.NewLine & "=== Fixed Version ===")
DemonstrateCorrectApproach()
End Sub

' <RaceConditionBad>
Shared Sub DemonstrateRaceCondition()
Dim total As Integer = 0
Dim numbers = Enumerable.Range(0, 10000)

' UNSAFE: Multiple threads writing to shared variable
numbers.AsParallel().ForAll(Sub(n) total += n)

Console.WriteLine($"Total (with race condition): {total}")
' Expected: 49,995,000 but result is unpredictable due to race condition
End Sub
' </RaceConditionBad>

' <RaceConditionGood>
Shared Sub DemonstrateCorrectApproach()
Dim numbers = Enumerable.Range(0, 10000)

' SAFE: Use thread-safe aggregate operation
Dim total As Integer = numbers.AsParallel().Sum()

Console.WriteLine($"Total (correct): {total}")
' Result is always 49,995,000
End Sub
' </RaceConditionGood>
End Class
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>RaceConditionExample</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

</Project>