diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
index 3911cc3..fce4bc6 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
@@ -1,4 +1,4 @@
-// https://github.com/xamarin/xamarin-android/blob/83854738b8e01747f9536f426fe17ad784cc2081/src/Xamarin.Android.Build.Tasks/Utilities/AsyncTaskExtensions.cs
+// https://github.com/xamarin/xamarin-android/blob/83854738b8e01747f9536f426fe17ad784cc2081/src/Xamarin.Android.Build.Tasks/Utilities/AsyncTaskExtensions.cs
using System;
using System.Collections.Generic;
@@ -12,17 +12,24 @@ public static class AsyncTaskExtensions
///
/// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll
///
- public static Task WhenAll(this AsyncTask asyncTask, IEnumerable source, Action body)
+ public static Task WhenAll (this AsyncTask asyncTask, IEnumerable source, Action body) =>
+ asyncTask.WhenAll (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel);
+
+ ///
+ /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll
+ ///
+ public static Task WhenAll(this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning)
{
+ var scheduler = GetTaskScheduler (maxConcurrencyLevel);
var tasks = new List ();
foreach (var s in source) {
- tasks.Add (Task.Run (() => {
+ tasks.Add (Task.Factory.StartNew (() => {
try {
body (s);
} catch (Exception exc) {
LogErrorAndCancel (asyncTask, exc);
}
- }, asyncTask.CancellationToken));
+ }, asyncTask.CancellationToken, creationOptions, scheduler));
}
return Task.WhenAll (tasks);
}
@@ -31,18 +38,26 @@ public static Task WhenAll(this AsyncTask asyncTask, IEnumerable
- public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body)
+ public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) =>
+ asyncTask.WhenAllWithLock (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel);
+
+ ///
+ /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll
+ /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject)
+ ///
+ public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning)
{
+ var scheduler = GetTaskScheduler (maxConcurrencyLevel);
var lockObject = new object ();
var tasks = new List ();
foreach (var s in source) {
- tasks.Add (Task.Run (() => {
+ tasks.Add (Task.Factory.StartNew (() => {
try {
body (s, lockObject);
} catch (Exception exc) {
LogErrorAndCancel (asyncTask, exc);
}
- }, asyncTask.CancellationToken));
+ }, asyncTask.CancellationToken, creationOptions, scheduler));
}
return Task.WhenAll (tasks);
}
@@ -50,9 +65,15 @@ public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerab
///
/// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling.
///
- public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body)
+ public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body) =>
+ asyncTask.ParallelForEach (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel);
+
+ ///
+ /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling.
+ ///
+ public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel)
{
- var options = ParallelOptions (asyncTask);
+ var options = ParallelOptions (asyncTask, maxConcurrencyLevel);
return Parallel.ForEach (source, options, s => {
try {
body (s);
@@ -66,9 +87,16 @@ public static ParallelLoopResult ParallelForEach (this AsyncTask asyncT
/// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling.
/// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject)
///
- public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body)
+ public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) =>
+ asyncTask.ParallelForEachWithLock (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel);
+
+ ///
+ /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling.
+ /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject)
+ ///
+ public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel)
{
- var options = ParallelOptions (asyncTask);
+ var options = ParallelOptions (asyncTask, maxConcurrencyLevel);
var lockObject = new object ();
return Parallel.ForEach (source, options, s => {
try {
@@ -79,11 +107,19 @@ public static ParallelLoopResult ParallelForEachWithLock (this AsyncTas
});
}
- static ParallelOptions ParallelOptions (AsyncTask asyncTask) => new ParallelOptions {
+ static ParallelOptions ParallelOptions (AsyncTask asyncTask, int maxConcurrencyLevel) => new ParallelOptions {
CancellationToken = asyncTask.CancellationToken,
- TaskScheduler = TaskScheduler.Default,
+ TaskScheduler = GetTaskScheduler (maxConcurrencyLevel),
};
+ static TaskScheduler GetTaskScheduler (int maxConcurrencyLevel)
+ {
+ var pair = new ConcurrentExclusiveSchedulerPair (TaskScheduler.Default, maxConcurrencyLevel);
+ return pair.ConcurrentScheduler;
+ }
+
+ static int DefaultMaxConcurrencyLevel => Math.Max (1, Environment.ProcessorCount - 1);
+
static void LogErrorAndCancel (AsyncTask asyncTask, Exception exc)
{
asyncTask.LogCodedError ("XA0000", Properties.Resources.XA0000_Exception, exc);
@@ -91,16 +127,27 @@ static void LogErrorAndCancel (AsyncTask asyncTask, Exception exc)
}
///
- /// Calls Task.Run() with a proper CancellationToken.
+ /// Calls Task.Factory.StartNew() with a proper CancellationToken, TaskScheduler, and TaskCreationOptions.LongRunning.
+ ///
+ public static Task RunTask (this AsyncTask asyncTask, Action body) =>
+ asyncTask.RunTask (body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel);
+
+ ///
+ /// Calls Task.Factory.StartNew() with a proper CancellationToken
///
- public static Task RunTask (this AsyncTask asyncTask, Action body) =>
- Task.Run (body, asyncTask.CancellationToken);
+ public static Task RunTask (this AsyncTask asyncTask, Action body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning) =>
+ Task.Factory.StartNew (body, asyncTask.CancellationToken, creationOptions, GetTaskScheduler (maxConcurrencyLevel));
+ ///
+ /// Calls Task.Factory.StartNew() with a proper CancellationToken, TaskScheduler, and TaskCreationOptions.LongRunning.
+ ///
+ public static Task RunTask (this AsyncTask asyncTask, Func body) =>
+ asyncTask.RunTask (body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel);
///
- /// Calls Task.Run() with a proper CancellationToken.
+ /// Calls Task.Factory.StartNew() with a proper CancellationToken.
///
- public static Task RunTask (this AsyncTask asyncTask, Func body) =>
- Task.Run (body, asyncTask.CancellationToken);
+ public static Task RunTask (this AsyncTask asyncTask, Func body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning) =>
+ Task.Factory.StartNew (body, asyncTask.CancellationToken, creationOptions, GetTaskScheduler (maxConcurrencyLevel));
}
}
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
new file mode 100644
index 0000000..ecfc866
--- /dev/null
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Android.Build.Tasks;
+using NUnit.Framework;
+using Xamarin.Build;
+
+namespace Microsoft.Android.Build.BaseTasks.Tests
+{
+ [TestFixture]
+ public class AsyncTaskExtensionsTests
+ {
+ const int Iterations = 32;
+
+ [Test]
+ public async Task RunTask ()
+ {
+ bool set = false;
+ await new AsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type
+ Assert.IsTrue (set);
+ }
+
+ [Test]
+ public async Task RunTaskOfT ()
+ {
+ bool set = false;
+ Assert.IsTrue (await new AsyncTask ().RunTask (() => set = true), "RunTask should return true");
+ Assert.IsTrue (set);
+ }
+
+ [Test]
+ public async Task WhenAll ()
+ {
+ bool set = false;
+ await new AsyncTask ().WhenAll (new [] { 0 }, _ => set = true);
+ Assert.IsTrue (set);
+ }
+
+ [Test]
+ public async Task WhenAllWithLock ()
+ {
+ var input = new int [Iterations];
+ var output = new List ();
+ await new AsyncTask ().WhenAllWithLock (input, (i, l) => {
+ lock (l) output.Add (i);
+ });
+ Assert.AreEqual (Iterations, output.Count);
+ }
+
+ [Test]
+ public void ParallelForEach ()
+ {
+ bool set = false;
+ new AsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true);
+ Assert.IsTrue (set);
+ }
+
+ [Test]
+ public void ParallelForEachWithLock ()
+ {
+ var input = new int [Iterations];
+ var output = new List ();
+ new AsyncTask ().ParallelForEachWithLock (input, (i, l) => {
+ lock (l) output.Add (i);
+ });
+ Assert.AreEqual (Iterations, output.Count);
+ }
+ }
+}