diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
index c38d7c36049db8..fe9348f4678320 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/ParallelTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/ParallelTests.cs
new file mode 100644
index 00000000000000..c74b84b60fc092
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/ParallelTests.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Runtime.InteropServices.JavaScript.Tests
+{
+ public static class ParallelTests
+ {
+ // The behavior of APIs like Invoke depends on how many items they are asked to invoke
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(5)]
+ [InlineData(32)]
+ [InlineData(250)]
+ public static void ParallelInvokeActionArray(int count)
+ {
+ var actions = new List();
+ int sum = 0, expected = 0;
+ for (int i = 0; i < count; i++) {
+ int j = i;
+ actions.Add(() => {
+ sum += j;
+ });
+ expected += j;
+ }
+
+ Parallel.Invoke(actions.ToArray());
+ Assert.Equal(expected, sum);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(32)]
+ [InlineData(250)]
+ public static void ParallelFor(int count)
+ {
+ int sum = 0, expected = 0;
+ for (int i = 0; i < count; i++)
+ expected += i;
+ Parallel.For(0, count, (i) => { sum += i; });
+ Assert.Equal(expected, sum);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(32)]
+ [InlineData(250)]
+ public static void ParallelForEach(int count)
+ {
+ int sum = 0, expected = 0;
+ var items = new List();
+ for (int i = 0; i < count; i++) {
+ items.Add(i);
+ expected += i;
+ }
+ Parallel.ForEach(items, (i) => { sum += i; });
+ Assert.Equal(expected, sum);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs
index cffded009d03ca..1b81cfbb8b9b4a 100644
--- a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs
+++ b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs
@@ -243,9 +243,12 @@ public static void Invoke(ParallelOptions parallelOptions, params Action[] actio
{
// If we've gotten this far, it's time to process the actions.
- // This is more efficient for a large number of actions, or for enforcing MaxDegreeOfParallelism:
- if ((actionsCopy.Length > SMALL_ACTIONCOUNT_LIMIT) ||
- (parallelOptions.MaxDegreeOfParallelism != -1 && parallelOptions.MaxDegreeOfParallelism < actionsCopy.Length))
+ // Web browsers need special treatment that is implemented in TaskReplicator
+ if (OperatingSystem.IsBrowser() ||
+ // This is more efficient for a large number of actions, or for enforcing MaxDegreeOfParallelism:
+ (actionsCopy.Length > SMALL_ACTIONCOUNT_LIMIT) ||
+ (parallelOptions.MaxDegreeOfParallelism != -1 && parallelOptions.MaxDegreeOfParallelism < actionsCopy.Length)
+ )
{
// Used to hold any exceptions encountered during action processing
ConcurrentQueue? exceptionQ = null; // will be lazily initialized if necessary
diff --git a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs
index c10e9c8b19b1c9..a84113bd775aa0 100644
--- a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs
+++ b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs
@@ -131,17 +131,29 @@ private TaskReplicator(ParallelOptions options, bool stopOnFirstFailure)
public static void Run(ReplicatableUserAction action, ParallelOptions options, bool stopOnFirstFailure)
{
- int maxConcurrencyLevel = (options.EffectiveMaxConcurrencyLevel > 0) ? options.EffectiveMaxConcurrencyLevel : int.MaxValue;
-
- TaskReplicator replicator = new TaskReplicator(options, stopOnFirstFailure);
- new Replica(replicator, maxConcurrencyLevel, CooperativeMultitaskingTaskTimeout_RootTask, action).Start();
-
- Replica? nextReplica;
- while (replicator._pendingReplicas.TryDequeue(out nextReplica))
- nextReplica.Wait();
-
- if (replicator._exceptions != null)
- throw new AggregateException(replicator._exceptions);
+ // Browser hosts do not support synchronous Wait so we want to run the
+ // replicated task directly instead of going through Task infrastructure
+ if (OperatingSystem.IsBrowser()) {
+ // Since we are running on a single thread, we don't want the action to time out
+ var timeout = int.MaxValue - 1;
+ var state = default(TState)!;
+
+ action(ref state, timeout, out bool yieldedBeforeCompletion);
+ if (yieldedBeforeCompletion)
+ throw new Exception("Replicated tasks cannot yield in this single-threaded browser environment");
+ } else {
+ int maxConcurrencyLevel = (options.EffectiveMaxConcurrencyLevel > 0) ? options.EffectiveMaxConcurrencyLevel : int.MaxValue;
+
+ TaskReplicator replicator = new TaskReplicator(options, stopOnFirstFailure);
+ new Replica(replicator, maxConcurrencyLevel, CooperativeMultitaskingTaskTimeout_RootTask, action).Start();
+
+ Replica? nextReplica;
+ while (replicator._pendingReplicas.TryDequeue(out nextReplica))
+ nextReplica.Wait();
+
+ if (replicator._exceptions != null)
+ throw new AggregateException(replicator._exceptions);
+ }
}
diff --git a/src/libraries/System.Threading.Tasks.Parallel/tests/BreakTests.cs b/src/libraries/System.Threading.Tasks.Parallel/tests/BreakTests.cs
index 91f1489f76d355..526f944a10350c 100644
--- a/src/libraries/System.Threading.Tasks.Parallel/tests/BreakTests.cs
+++ b/src/libraries/System.Threading.Tasks.Parallel/tests/BreakTests.cs
@@ -10,7 +10,7 @@ namespace System.Threading.Tasks.Tests
{
public static class BreakTests
{
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(100, 10)]
[InlineData(100, 20)]
[InlineData(1000, 100)]
@@ -46,7 +46,7 @@ public static void TestFor_Break_Basic(int loopsize, int breakpoint)
Assert.True(result, "TestForBreak: Failed: Could not detect any interruption of For-loop.");
}
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(100, 10)]
[InlineData(100, 20)]
[InlineData(1000, 100)]
@@ -86,7 +86,7 @@ public static void TestFor_Break_64Bits(int loopsize, int breakpoint)
Assert.True(result, "TestFor64Break: Failed: Could not detect any interruption of For-loop.");
}
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(500, 10)]
[InlineData(500, 20)]
[InlineData(1000, 100)]
diff --git a/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelFor.cs b/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelFor.cs
index b8a97b90c2d395..34bc14d7d87214 100644
--- a/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelFor.cs
+++ b/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelFor.cs
@@ -7,7 +7,7 @@ namespace System.Threading.Tasks.Tests
{
public static class ParallelForUnitTests
{
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(API.For64, StartIndexBase.Int32, 0, WithParallelOption.None, ActionWithState.None, ActionWithLocal.None)]
[InlineData(API.For64, StartIndexBase.Int32, 10, WithParallelOption.None, ActionWithState.Stop, ActionWithLocal.HasFinally)]
[InlineData(API.For64, StartIndexBase.Int32, 10, WithParallelOption.WithDOP, ActionWithState.None, ActionWithLocal.None)]