From f57b9cfec90769cb05cd4e789f5e235b261a5aff Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Tue, 11 Jun 2024 16:38:23 -0400
Subject: [PATCH 1/8] [BaseTasks] Import Xamarin.Build.AsyncTask

We've decided to move https://github.com/xamarin/Xamarin.Build.AsyncTask
into this repository as part of dotnet org migration efforts.

Source and test content from xamarin/Xamarin.Build.AsyncTask@db4ce14dac
has been added to Microsoft.Android.Build.BaseTasks.
---
 .../AndroidAsyncTask.cs                       |   1 -
 .../AsyncTask.cs                              | 390 ++++++++++++++++++
 .../AsyncTaskExtensions.cs                    |   1 -
 .../MSBuildExtensions.cs                      |   1 -
 .../MSBuildReferences.projitems               |   1 -
 .../UnhandledExceptionLogger.cs               |   1 -
 .../AsyncTaskExtensionsTests.cs               |   1 -
 .../AsyncTaskTests.cs                         |  61 +++
 8 files changed, 451 insertions(+), 6 deletions(-)
 create mode 100644 src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
 create mode 100644 tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs

diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
index a04d86ff..f10cb687 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
@@ -1,7 +1,6 @@
 // https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27
 
 using System;
-using Xamarin.Build;
 using static System.Threading.Tasks.TaskExtensions;
 
 namespace Microsoft.Android.Build.Tasks
diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
new file mode 100644
index 00000000..9b958d36
--- /dev/null
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -0,0 +1,390 @@
+// https://github.com/xamarin/Xamarin.Build.AsyncTask/blob/db4ce14dacfef47435c238b1b681c124e60ea1a0/Xamarin.Build.AsyncTask/AsyncTask.cs
+
+using System;
+using System.IO;
+using Microsoft.Build.Utilities;
+using Microsoft.Build.Framework;
+using System.Threading;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.Android.Build.Tasks
+{
+    /// <summary>
+    /// Base class for tasks that need long-running cancellable asynchronous tasks 
+    /// that don't block the UI thread in the IDE.
+    /// </summary>
+    public class AsyncTask : Task, ICancelableTask
+    {
+        readonly CancellationTokenSource cts = new CancellationTokenSource();
+        readonly Queue logMessageQueue = new Queue();
+        readonly Queue warningMessageQueue = new Queue();
+        readonly Queue errorMessageQueue = new Queue();
+        readonly Queue customMessageQueue = new Queue();
+        readonly Queue telemetryMessageQueue = new Queue ();
+        readonly ManualResetEvent logDataAvailable = new ManualResetEvent(false);
+        readonly ManualResetEvent errorDataAvailable = new ManualResetEvent(false);
+        readonly ManualResetEvent warningDataAvailable = new ManualResetEvent(false);
+        readonly ManualResetEvent customDataAvailable = new ManualResetEvent(false);
+        readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent(false);
+        readonly ManualResetEvent taskCancelled = new ManualResetEvent(false);
+        readonly ManualResetEvent completed = new ManualResetEvent(false);
+        bool isRunning = true;
+        object eventlock = new object();
+        int uiThreadId = 0;
+
+        /// <summary>
+        /// Indicates if the task will yield the node during tool execution.
+        /// </summary>
+        public bool YieldDuringToolExecution { get; set; }
+
+        /// <summary>
+        /// The cancellation token to notify the cancellation requests
+        /// </summary>
+        public CancellationToken CancellationToken => cts.Token;
+
+        /// <summary>
+        /// Gets the current working directory for the task, which is captured at task 
+        /// creation time from <see cref="Directory.GetCurrentDirectory"/>.
+        /// </summary>
+        protected string WorkingDirectory { get; private set; }
+
+        [Obsolete("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")]
+        private new TaskLoggingHelper Log => base.Log;
+
+        /// <summary>
+        /// Initializes the task.
+        /// </summary>
+        public AsyncTask()
+        {
+            YieldDuringToolExecution = false;
+            WorkingDirectory = Directory.GetCurrentDirectory();
+            uiThreadId = Thread.CurrentThread.ManagedThreadId;
+        }
+
+        public void Cancel() => taskCancelled.Set();
+
+        protected void Complete(System.Threading.Tasks.Task task)
+        {
+            if (task.Exception != null)
+            {
+                var ex = task.Exception.GetBaseException();
+                LogError(ex.Message + Environment.NewLine + ex.StackTrace);
+            }
+            Complete();
+        }
+
+        public void Complete() => completed.Set();
+
+        public void LogDebugTaskItems(string message, string[] items)
+        {
+            LogDebugMessage(message);
+
+            if (items == null)
+                return;
+
+            foreach (var item in items)
+                LogDebugMessage("    {0}", item);
+        }
+
+        public void LogDebugTaskItems(string message, ITaskItem[] items)
+        {
+            LogDebugMessage(message);
+
+            if (items == null)
+                return;
+
+            foreach (var item in items)
+                LogDebugMessage("    {0}", item.ItemSpec);
+        }
+
+        public void LogTelemetry(string eventName, IDictionary<string, string> properties)
+        {
+            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+            {
+#pragma warning disable 618
+                Log.LogTelemetry(eventName, properties);
+                return;
+#pragma warning restore 618
+            }
+
+            var data = new TelemetryEventArgs() {
+                    EventName = eventName,
+                    Properties = properties
+            };
+            EnqueueMessage(telemetryMessageQueue, data, telemetryDataAvailable);
+        }
+
+        public void LogMessage(string message) => LogMessage(message, importance: MessageImportance.Normal);
+
+        public void LogMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs));
+
+        public void LogDebugMessage(string message) => LogMessage(message, importance: MessageImportance.Low);
+
+        public void LogDebugMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs), importance: MessageImportance.Low);
+
+        public void LogMessage(string message, MessageImportance importance = MessageImportance.Normal)
+        {
+            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+            {
+#pragma warning disable 618
+                Log.LogMessage(importance, message);
+                return;
+#pragma warning restore 618
+            }
+
+            var data = new BuildMessageEventArgs(
+                    message: message,
+                    helpKeyword: null,
+                    senderName: null,
+                    importance: importance
+            );
+            EnqueueMessage(logMessageQueue, data, logDataAvailable);
+        }
+
+        public void LogError(string message) => LogCodedError(code: null, message: message, file: null, lineNumber: 0);
+
+        public void LogError(string message, params object[] messageArgs) => LogCodedError(code: null, message: string.Format(message, messageArgs));
+
+        public void LogCodedError(string code, string message) => LogCodedError(code: code, message: message, file: null, lineNumber: 0);
+
+        public void LogCodedError(string code, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
+
+        public void LogCodedError(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
+
+        public void LogCodedError(string code, string message, string file, int lineNumber)
+        {
+            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+            {
+#pragma warning disable 618
+                Log.LogError(
+                    subcategory: null,
+                    errorCode: code,
+                    helpKeyword: null,
+                    file: file,
+                    lineNumber: lineNumber,
+                    columnNumber: 0,
+                    endLineNumber: 0,
+                    endColumnNumber: 0,
+                    message: message
+                );
+                return;
+#pragma warning restore 618
+            }
+
+            var data = new BuildErrorEventArgs(
+                    subcategory: null,
+                    code: code,
+                    file: file,
+                    lineNumber: lineNumber,
+                    columnNumber: 0,
+                    endLineNumber: 0,
+                    endColumnNumber: 0,
+                    message: message,
+                    helpKeyword: null,
+                    senderName: null
+            );
+            EnqueueMessage(errorMessageQueue, data, errorDataAvailable);
+        }
+
+        public void LogWarning(string message) => LogCodedWarning(code: null, message: message, file: null, lineNumber: 0);
+
+        public void LogWarning(string message, params object[] messageArgs) => LogCodedWarning(code: null, message: string.Format(message, messageArgs));
+
+        public void LogCodedWarning(string code, string message) => LogCodedWarning(code: code, message: message, file: null, lineNumber: 0);
+
+        public void LogCodedWarning(string code, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
+
+        public void LogCodedWarning(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
+
+        public void LogCodedWarning(string code, string message, string file, int lineNumber)
+        {
+            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+            {
+#pragma warning disable 618
+                Log.LogWarning(
+                    subcategory: null,
+                    warningCode: code,
+                    helpKeyword: null,
+                    file: file,
+                    lineNumber: lineNumber,
+                    columnNumber: 0,
+                    endLineNumber: 0,
+                    endColumnNumber: 0,
+                    message: message
+                );
+                return;
+#pragma warning restore 618
+            }
+            var data = new BuildWarningEventArgs(
+                    subcategory: null,
+                    code: code,
+                    file: file,
+                    lineNumber: lineNumber,
+                    columnNumber: 0,
+                    endLineNumber: 0,
+                    endColumnNumber: 0,
+                    message: message,
+                    helpKeyword: null,
+                    senderName: null
+            );
+            EnqueueMessage(warningMessageQueue, data, warningDataAvailable);
+        }
+
+        public void LogCustomBuildEvent(CustomBuildEventArgs e)
+        {
+            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+            {
+#pragma warning disable 618
+                BuildEngine.LogCustomEvent(e);
+                return;
+#pragma warning restore 618
+            }
+            EnqueueMessage(customMessageQueue, e, customDataAvailable);
+        }
+
+        public override bool Execute()
+        {
+            WaitForCompletion();
+#pragma warning disable 618
+            return !Log.HasLoggedErrors;
+#pragma warning restore 618
+        }
+
+        private void EnqueueMessage(Queue queue, object item, ManualResetEvent resetEvent)
+        {
+            lock (queue.SyncRoot)
+            {
+                queue.Enqueue(item);
+                lock (eventlock)
+                {
+                    if (isRunning)
+                        resetEvent.Set();
+                }
+            }
+        }
+
+        private void LogInternal<T>(Queue queue, Action<T> action, ManualResetEvent resetEvent)
+        {
+            lock (queue.SyncRoot)
+            {
+                while (queue.Count > 0)
+                {
+                    var args = (T)queue.Dequeue();
+                    action(args);
+                }
+                resetEvent.Reset();
+            }
+        }
+
+        protected void Yield()
+        {
+            if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
+                ((IBuildEngine3)BuildEngine).Yield();
+        }
+
+        protected void Reacquire()
+        {
+            if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
+                ((IBuildEngine3)BuildEngine).Reacquire();
+        }
+
+        protected void WaitForCompletion()
+        {
+            WaitHandle[] handles = new WaitHandle[] {
+                logDataAvailable,
+                errorDataAvailable,
+                warningDataAvailable,
+                customDataAvailable,
+                telemetryDataAvailable,
+                taskCancelled,
+                completed,
+            };
+            try
+            {
+                while (isRunning)
+                {
+                    var index = (WaitHandleIndex)System.Threading.WaitHandle.WaitAny(handles, TimeSpan.FromMilliseconds(10));
+                    switch (index)
+                    {
+                        case WaitHandleIndex.LogDataAvailable:
+                            LogInternal<BuildMessageEventArgs>(logMessageQueue, (e) =>
+                            {
+#pragma warning disable 618
+                                Log.LogMessage(e.Importance, e.Message);
+#pragma warning restore 618
+                            }, logDataAvailable);
+                            break;
+                        case WaitHandleIndex.ErrorDataAvailable:
+                            LogInternal<BuildErrorEventArgs>(errorMessageQueue, (e) =>
+                            {
+#pragma warning disable 618
+                                Log.LogError(subcategory: null,
+                                  errorCode: e.Code,
+                                  helpKeyword: null,
+                                  file: e.File,
+                                  lineNumber: e.LineNumber,
+                                  columnNumber: e.ColumnNumber,
+                                  endLineNumber: e.EndLineNumber,
+                                  endColumnNumber: e.EndColumnNumber,
+                                  message: e.Message);
+#pragma warning restore 618
+                            }, errorDataAvailable);
+                            break;
+                        case WaitHandleIndex.WarningDataAvailable:
+                            LogInternal<BuildWarningEventArgs>(warningMessageQueue, (e) =>
+                            {
+#pragma warning disable 618
+                                Log.LogWarning(subcategory: null,
+                                  warningCode: e.Code,
+                                  helpKeyword: null,
+                                  file: e.File,
+                                  lineNumber: e.LineNumber,
+                                  columnNumber: e.ColumnNumber,
+                                  endLineNumber: e.EndLineNumber,
+                                  endColumnNumber: e.EndColumnNumber,
+                                  message: e.Message);
+#pragma warning restore 618
+                            }, warningDataAvailable);
+                            break;
+                        case WaitHandleIndex.CustomDataAvailable:
+                            LogInternal<CustomBuildEventArgs>(customMessageQueue, (e) =>
+                            {
+                                BuildEngine.LogCustomEvent(e);
+                            }, customDataAvailable);
+                            break;
+                        case WaitHandleIndex.TelemetryDataAvailable:
+                            LogInternal<TelemetryEventArgs>(telemetryMessageQueue, (e) => {
+                                BuildEngine5.LogTelemetry(e.EventName, e.Properties);
+                            }, telemetryDataAvailable);
+                            break;
+                        case WaitHandleIndex.TaskCancelled:
+                            Cancel();
+                            cts.Cancel();
+                            isRunning = false;
+                            break;
+                        case WaitHandleIndex.Completed:
+                            isRunning = false;
+                            break;
+                    }
+                }
+
+            }
+            finally
+            {
+
+            }
+        }
+
+        private enum WaitHandleIndex
+        {
+            LogDataAvailable,
+            ErrorDataAvailable,
+            WarningDataAvailable,
+            CustomDataAvailable,
+            TelemetryDataAvailable,
+            TaskCancelled,
+            Completed,
+        }
+    }
+}
diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
index fce4bc64..6b643ee7 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
@@ -3,7 +3,6 @@
 using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
-using Xamarin.Build;
 
 namespace Microsoft.Android.Build.Tasks
 {
diff --git a/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
index fd0909d7..323c874e 100644
--- a/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
@@ -9,7 +9,6 @@
 
 using Microsoft.Build.Utilities;
 using Microsoft.Build.Framework;
-using Xamarin.Build;
 
 namespace Microsoft.Android.Build.Tasks
 {
diff --git a/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems
index 96c56c0e..5d6a62a5 100644
--- a/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems
+++ b/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems
@@ -17,7 +17,6 @@
     <PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MSBuildPackageReferenceVersion)" />
     <PackageReference Include="System.Security.Cryptography.Xml" Version="$(SystemSecurityCryptographyXmlVersion)" />
     <PackageReference Include="K4os.Compression.LZ4" Version="1.1.11" />
-    <PackageReference Include="Xamarin.Build.AsyncTask" Version="0.4.7" GeneratePathProperty="true" />
     <PackageReference Include="Xamarin.LibZipSharp" Version="$(LibZipSharpVersion)" GeneratePathProperty="true" />
     <PackageReference Include="Mono.Unix" Version="$(MonoUnixVersion)" GeneratePathProperty="true" />
   </ItemGroup>
diff --git a/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs
index 67d20868..4cfe3c0b 100644
--- a/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs
@@ -5,7 +5,6 @@
 using System.IO;
 using System.Threading.Tasks;
 using Microsoft.Build.Utilities;
-using Xamarin.Build;
 
 namespace Microsoft.Android.Build.Tasks
 {
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
index ecfc8667..6f9d3c30 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
@@ -2,7 +2,6 @@
 using System.Threading.Tasks;
 using Microsoft.Android.Build.Tasks;
 using NUnit.Framework;
-using Xamarin.Build;
 
 namespace Microsoft.Android.Build.BaseTasks.Tests
 {
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
new file mode 100644
index 00000000..e4dc75a8
--- /dev/null
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Linq;
+using Microsoft.Android.Build.BaseTasks.Tests.Utilities;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using NUnit.Framework;
+
+namespace Microsoft.Android.Build.BaseTasks.Tests
+{
+	[TestFixture]
+	public class AsyncTaskTests
+	{
+		List<BuildErrorEventArgs> errors;
+		List<BuildWarningEventArgs> warnings;
+		List<BuildMessageEventArgs> messages;
+		MockBuildEngine engine;
+
+		[SetUp]
+		public void TestSetup()
+		{
+			errors = new List<BuildErrorEventArgs> ();
+			warnings = new List<BuildWarningEventArgs> ();
+			messages = new List<BuildMessageEventArgs> ();
+			engine = new MockBuildEngine (TestContext.Out, errors, warnings, messages);
+		}
+
+		public class AsyncMessage : AsyncTask
+		{
+			public string Text { get; set; }
+
+			public override bool Execute()
+			{
+				Task.Run(async () =>
+				{
+					await Task.Delay(5000);
+					LogMessage(Text);
+					Complete();
+				});
+
+				LogTelemetry("Test", new System.Collections.Generic.Dictionary<string, string> () {{"Property", "Value"}});
+
+				return base.Execute();
+			}
+		}
+
+		[Test]
+		public void RunAsyncMessage ()
+		{
+			var message = "Hello Async World!";
+			var task = new AsyncMessage () {
+				BuildEngine = engine,
+				Text = message
+			};
+			var taskSucceeded = task.Execute ();
+			Assert.IsTrue (messages.Any (e => e.Message.Contains (message)),
+				$"Task did not contain expected message text: '{message}'.");
+		}
+
+	}
+}

From 02e2827c95b3621e9e96de7136c10217b1a05dc3 Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Tue, 11 Jun 2024 17:44:41 -0400
Subject: [PATCH 2/8] Add LogErrorFromException From android-sdk-installer

---
 .../AsyncTask.cs                              | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
index 9b958d36..4a0588e1 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -1,7 +1,9 @@
 // https://github.com/xamarin/Xamarin.Build.AsyncTask/blob/db4ce14dacfef47435c238b1b681c124e60ea1a0/Xamarin.Build.AsyncTask/AsyncTask.cs
 
 using System;
+using System.Diagnostics;
 using System.IO;
+using System.Linq;
 using Microsoft.Build.Utilities;
 using Microsoft.Build.Framework;
 using System.Threading;
@@ -146,6 +148,45 @@ public void LogMessage(string message, MessageImportance importance = MessageImp
 
         public void LogError(string message, params object[] messageArgs) => LogCodedError(code: null, message: string.Format(message, messageArgs));
 
+        public void LogErrorFromException(Exception exception)
+        {
+            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+            {
+#pragma warning disable 618
+                Log.LogErrorFromException(exception);
+                return;
+#pragma warning restore 618
+            }
+
+            StackFrame exceptionFrame = null;
+            try
+            {
+                exceptionFrame = new StackTrace(exception, true)?.GetFrames()?.FirstOrDefault();
+            }
+            catch { }
+
+            lock (errorMessageQueue.SyncRoot)
+            {
+                errorMessageQueue.Enqueue(new BuildErrorEventArgs(
+                    subcategory: null,
+                    code: null,
+                    file: exceptionFrame?.GetFileName(),
+                    lineNumber: exceptionFrame?.GetFileLineNumber() ?? 0,
+                    columnNumber: exceptionFrame?.GetFileColumnNumber() ?? 0,
+                    endLineNumber: 0,
+                    endColumnNumber: 0,
+                    message: exception.Message,
+                    helpKeyword: null,
+                    senderName: null
+                ));
+                lock (eventlock)
+                {
+                    if (isRunning)
+                        errorDataAvailable.Set();
+                }
+            }
+        }
+
         public void LogCodedError(string code, string message) => LogCodedError(code: code, message: message, file: null, lineNumber: 0);
 
         public void LogCodedError(string code, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);

From 4cf94c3e6d9ecf71fa01abf2232b04bb59e68f23 Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Tue, 11 Jun 2024 18:50:03 -0400
Subject: [PATCH 3/8] Formatting

---
 .../AsyncTask.cs                              | 756 +++++++++---------
 1 file changed, 378 insertions(+), 378 deletions(-)

diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
index 4a0588e1..df518c99 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -12,420 +12,420 @@
 
 namespace Microsoft.Android.Build.Tasks
 {
-    /// <summary>
-    /// Base class for tasks that need long-running cancellable asynchronous tasks 
-    /// that don't block the UI thread in the IDE.
-    /// </summary>
-    public class AsyncTask : Task, ICancelableTask
-    {
-        readonly CancellationTokenSource cts = new CancellationTokenSource();
-        readonly Queue logMessageQueue = new Queue();
-        readonly Queue warningMessageQueue = new Queue();
-        readonly Queue errorMessageQueue = new Queue();
-        readonly Queue customMessageQueue = new Queue();
-        readonly Queue telemetryMessageQueue = new Queue ();
-        readonly ManualResetEvent logDataAvailable = new ManualResetEvent(false);
-        readonly ManualResetEvent errorDataAvailable = new ManualResetEvent(false);
-        readonly ManualResetEvent warningDataAvailable = new ManualResetEvent(false);
-        readonly ManualResetEvent customDataAvailable = new ManualResetEvent(false);
-        readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent(false);
-        readonly ManualResetEvent taskCancelled = new ManualResetEvent(false);
-        readonly ManualResetEvent completed = new ManualResetEvent(false);
-        bool isRunning = true;
-        object eventlock = new object();
-        int uiThreadId = 0;
-
-        /// <summary>
-        /// Indicates if the task will yield the node during tool execution.
-        /// </summary>
-        public bool YieldDuringToolExecution { get; set; }
-
-        /// <summary>
-        /// The cancellation token to notify the cancellation requests
-        /// </summary>
-        public CancellationToken CancellationToken => cts.Token;
-
-        /// <summary>
-        /// Gets the current working directory for the task, which is captured at task 
-        /// creation time from <see cref="Directory.GetCurrentDirectory"/>.
-        /// </summary>
-        protected string WorkingDirectory { get; private set; }
-
-        [Obsolete("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")]
-        private new TaskLoggingHelper Log => base.Log;
-
-        /// <summary>
-        /// Initializes the task.
-        /// </summary>
-        public AsyncTask()
-        {
-            YieldDuringToolExecution = false;
-            WorkingDirectory = Directory.GetCurrentDirectory();
-            uiThreadId = Thread.CurrentThread.ManagedThreadId;
-        }
-
-        public void Cancel() => taskCancelled.Set();
-
-        protected void Complete(System.Threading.Tasks.Task task)
-        {
-            if (task.Exception != null)
-            {
-                var ex = task.Exception.GetBaseException();
-                LogError(ex.Message + Environment.NewLine + ex.StackTrace);
-            }
-            Complete();
-        }
-
-        public void Complete() => completed.Set();
-
-        public void LogDebugTaskItems(string message, string[] items)
-        {
-            LogDebugMessage(message);
-
-            if (items == null)
-                return;
-
-            foreach (var item in items)
-                LogDebugMessage("    {0}", item);
-        }
-
-        public void LogDebugTaskItems(string message, ITaskItem[] items)
-        {
-            LogDebugMessage(message);
-
-            if (items == null)
-                return;
-
-            foreach (var item in items)
-                LogDebugMessage("    {0}", item.ItemSpec);
-        }
-
-        public void LogTelemetry(string eventName, IDictionary<string, string> properties)
-        {
-            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-            {
+	/// <summary>
+	/// Base class for tasks that need long-running cancellable asynchronous tasks 
+	/// that don't block the UI thread in the IDE.
+	/// </summary>
+	public class AsyncTask : Task, ICancelableTask
+	{
+		readonly CancellationTokenSource cts = new CancellationTokenSource();
+		readonly Queue logMessageQueue = new Queue();
+		readonly Queue warningMessageQueue = new Queue();
+		readonly Queue errorMessageQueue = new Queue();
+		readonly Queue customMessageQueue = new Queue();
+		readonly Queue telemetryMessageQueue = new Queue ();
+		readonly ManualResetEvent logDataAvailable = new ManualResetEvent(false);
+		readonly ManualResetEvent errorDataAvailable = new ManualResetEvent(false);
+		readonly ManualResetEvent warningDataAvailable = new ManualResetEvent(false);
+		readonly ManualResetEvent customDataAvailable = new ManualResetEvent(false);
+		readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent(false);
+		readonly ManualResetEvent taskCancelled = new ManualResetEvent(false);
+		readonly ManualResetEvent completed = new ManualResetEvent(false);
+		bool isRunning = true;
+		object eventlock = new object();
+		int uiThreadId = 0;
+
+		/// <summary>
+		/// Indicates if the task will yield the node during tool execution.
+		/// </summary>
+		public bool YieldDuringToolExecution { get; set; }
+
+		/// <summary>
+		/// The cancellation token to notify the cancellation requests
+		/// </summary>
+		public CancellationToken CancellationToken => cts.Token;
+
+		/// <summary>
+		/// Gets the current working directory for the task, which is captured at task 
+		/// creation time from <see cref="Directory.GetCurrentDirectory"/>.
+		/// </summary>
+		protected string WorkingDirectory { get; private set; }
+
+		[Obsolete("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")]
+		private new TaskLoggingHelper Log => base.Log;
+
+		/// <summary>
+		/// Initializes the task.
+		/// </summary>
+		public AsyncTask()
+		{
+			YieldDuringToolExecution = false;
+			WorkingDirectory = Directory.GetCurrentDirectory();
+			uiThreadId = Thread.CurrentThread.ManagedThreadId;
+		}
+
+		public void Cancel() => taskCancelled.Set();
+
+		protected void Complete(System.Threading.Tasks.Task task)
+		{
+			if (task.Exception != null)
+			{
+				var ex = task.Exception.GetBaseException();
+				LogError(ex.Message + Environment.NewLine + ex.StackTrace);
+			}
+			Complete();
+		}
+
+		public void Complete() => completed.Set();
+
+		public void LogDebugTaskItems(string message, string[] items)
+		{
+			LogDebugMessage(message);
+
+			if (items == null)
+				return;
+
+			foreach (var item in items)
+				LogDebugMessage("	{0}", item);
+		}
+
+		public void LogDebugTaskItems(string message, ITaskItem[] items)
+		{
+			LogDebugMessage(message);
+
+			if (items == null)
+				return;
+
+			foreach (var item in items)
+				LogDebugMessage("	{0}", item.ItemSpec);
+		}
+
+		public void LogTelemetry(string eventName, IDictionary<string, string> properties)
+		{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+			{
 #pragma warning disable 618
-                Log.LogTelemetry(eventName, properties);
-                return;
+				Log.LogTelemetry(eventName, properties);
+				return;
 #pragma warning restore 618
-            }
+			}
 
-            var data = new TelemetryEventArgs() {
-                    EventName = eventName,
-                    Properties = properties
-            };
-            EnqueueMessage(telemetryMessageQueue, data, telemetryDataAvailable);
-        }
+			var data = new TelemetryEventArgs() {
+					EventName = eventName,
+					Properties = properties
+			};
+			EnqueueMessage(telemetryMessageQueue, data, telemetryDataAvailable);
+		}
 
-        public void LogMessage(string message) => LogMessage(message, importance: MessageImportance.Normal);
+		public void LogMessage(string message) => LogMessage(message, importance: MessageImportance.Normal);
 
-        public void LogMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs));
+		public void LogMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs));
 
-        public void LogDebugMessage(string message) => LogMessage(message, importance: MessageImportance.Low);
+		public void LogDebugMessage(string message) => LogMessage(message, importance: MessageImportance.Low);
 
-        public void LogDebugMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs), importance: MessageImportance.Low);
+		public void LogDebugMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs), importance: MessageImportance.Low);
 
-        public void LogMessage(string message, MessageImportance importance = MessageImportance.Normal)
-        {
-            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-            {
+		public void LogMessage(string message, MessageImportance importance = MessageImportance.Normal)
+		{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+			{
 #pragma warning disable 618
-                Log.LogMessage(importance, message);
-                return;
+				Log.LogMessage(importance, message);
+				return;
 #pragma warning restore 618
-            }
+			}
 
-            var data = new BuildMessageEventArgs(
-                    message: message,
-                    helpKeyword: null,
-                    senderName: null,
-                    importance: importance
-            );
-            EnqueueMessage(logMessageQueue, data, logDataAvailable);
-        }
+			var data = new BuildMessageEventArgs(
+					message: message,
+					helpKeyword: null,
+					senderName: null,
+					importance: importance
+			);
+			EnqueueMessage(logMessageQueue, data, logDataAvailable);
+		}
 
-        public void LogError(string message) => LogCodedError(code: null, message: message, file: null, lineNumber: 0);
+		public void LogError(string message) => LogCodedError(code: null, message: message, file: null, lineNumber: 0);
 
-        public void LogError(string message, params object[] messageArgs) => LogCodedError(code: null, message: string.Format(message, messageArgs));
+		public void LogError(string message, params object[] messageArgs) => LogCodedError(code: null, message: string.Format(message, messageArgs));
 
-        public void LogErrorFromException(Exception exception)
-        {
-            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-            {
+		public void LogErrorFromException(Exception exception)
+		{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+			{
 #pragma warning disable 618
-                Log.LogErrorFromException(exception);
-                return;
+				Log.LogErrorFromException(exception);
+				return;
 #pragma warning restore 618
-            }
-
-            StackFrame exceptionFrame = null;
-            try
-            {
-                exceptionFrame = new StackTrace(exception, true)?.GetFrames()?.FirstOrDefault();
-            }
-            catch { }
-
-            lock (errorMessageQueue.SyncRoot)
-            {
-                errorMessageQueue.Enqueue(new BuildErrorEventArgs(
-                    subcategory: null,
-                    code: null,
-                    file: exceptionFrame?.GetFileName(),
-                    lineNumber: exceptionFrame?.GetFileLineNumber() ?? 0,
-                    columnNumber: exceptionFrame?.GetFileColumnNumber() ?? 0,
-                    endLineNumber: 0,
-                    endColumnNumber: 0,
-                    message: exception.Message,
-                    helpKeyword: null,
-                    senderName: null
-                ));
-                lock (eventlock)
-                {
-                    if (isRunning)
-                        errorDataAvailable.Set();
-                }
-            }
-        }
-
-        public void LogCodedError(string code, string message) => LogCodedError(code: code, message: message, file: null, lineNumber: 0);
-
-        public void LogCodedError(string code, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
-
-        public void LogCodedError(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
-
-        public void LogCodedError(string code, string message, string file, int lineNumber)
-        {
-            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-            {
+			}
+
+			StackFrame exceptionFrame = null;
+			try
+			{
+				exceptionFrame = new StackTrace(exception, true)?.GetFrames()?.FirstOrDefault();
+			}
+			catch { }
+
+			lock (errorMessageQueue.SyncRoot)
+			{
+				errorMessageQueue.Enqueue(new BuildErrorEventArgs(
+					subcategory: null,
+					code: null,
+					file: exceptionFrame?.GetFileName(),
+					lineNumber: exceptionFrame?.GetFileLineNumber() ?? 0,
+					columnNumber: exceptionFrame?.GetFileColumnNumber() ?? 0,
+					endLineNumber: 0,
+					endColumnNumber: 0,
+					message: exception.Message,
+					helpKeyword: null,
+					senderName: null
+				));
+				lock (eventlock)
+				{
+					if (isRunning)
+						errorDataAvailable.Set();
+				}
+			}
+		}
+
+		public void LogCodedError(string code, string message) => LogCodedError(code: code, message: message, file: null, lineNumber: 0);
+
+		public void LogCodedError(string code, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
+
+		public void LogCodedError(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
+
+		public void LogCodedError(string code, string message, string file, int lineNumber)
+		{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+			{
 #pragma warning disable 618
-                Log.LogError(
-                    subcategory: null,
-                    errorCode: code,
-                    helpKeyword: null,
-                    file: file,
-                    lineNumber: lineNumber,
-                    columnNumber: 0,
-                    endLineNumber: 0,
-                    endColumnNumber: 0,
-                    message: message
-                );
-                return;
+				Log.LogError(
+					subcategory: null,
+					errorCode: code,
+					helpKeyword: null,
+					file: file,
+					lineNumber: lineNumber,
+					columnNumber: 0,
+					endLineNumber: 0,
+					endColumnNumber: 0,
+					message: message
+				);
+				return;
 #pragma warning restore 618
-            }
+			}
 
-            var data = new BuildErrorEventArgs(
-                    subcategory: null,
-                    code: code,
-                    file: file,
-                    lineNumber: lineNumber,
-                    columnNumber: 0,
-                    endLineNumber: 0,
-                    endColumnNumber: 0,
-                    message: message,
-                    helpKeyword: null,
-                    senderName: null
-            );
-            EnqueueMessage(errorMessageQueue, data, errorDataAvailable);
-        }
+			var data = new BuildErrorEventArgs(
+					subcategory: null,
+					code: code,
+					file: file,
+					lineNumber: lineNumber,
+					columnNumber: 0,
+					endLineNumber: 0,
+					endColumnNumber: 0,
+					message: message,
+					helpKeyword: null,
+					senderName: null
+			);
+			EnqueueMessage(errorMessageQueue, data, errorDataAvailable);
+		}
 
-        public void LogWarning(string message) => LogCodedWarning(code: null, message: message, file: null, lineNumber: 0);
+		public void LogWarning(string message) => LogCodedWarning(code: null, message: message, file: null, lineNumber: 0);
 
-        public void LogWarning(string message, params object[] messageArgs) => LogCodedWarning(code: null, message: string.Format(message, messageArgs));
+		public void LogWarning(string message, params object[] messageArgs) => LogCodedWarning(code: null, message: string.Format(message, messageArgs));
 
-        public void LogCodedWarning(string code, string message) => LogCodedWarning(code: code, message: message, file: null, lineNumber: 0);
+		public void LogCodedWarning(string code, string message) => LogCodedWarning(code: code, message: message, file: null, lineNumber: 0);
 
-        public void LogCodedWarning(string code, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
+		public void LogCodedWarning(string code, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
 
-        public void LogCodedWarning(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
+		public void LogCodedWarning(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
 
-        public void LogCodedWarning(string code, string message, string file, int lineNumber)
-        {
-            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-            {
+		public void LogCodedWarning(string code, string message, string file, int lineNumber)
+		{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+			{
 #pragma warning disable 618
-                Log.LogWarning(
-                    subcategory: null,
-                    warningCode: code,
-                    helpKeyword: null,
-                    file: file,
-                    lineNumber: lineNumber,
-                    columnNumber: 0,
-                    endLineNumber: 0,
-                    endColumnNumber: 0,
-                    message: message
-                );
-                return;
+				Log.LogWarning(
+					subcategory: null,
+					warningCode: code,
+					helpKeyword: null,
+					file: file,
+					lineNumber: lineNumber,
+					columnNumber: 0,
+					endLineNumber: 0,
+					endColumnNumber: 0,
+					message: message
+				);
+				return;
 #pragma warning restore 618
-            }
-            var data = new BuildWarningEventArgs(
-                    subcategory: null,
-                    code: code,
-                    file: file,
-                    lineNumber: lineNumber,
-                    columnNumber: 0,
-                    endLineNumber: 0,
-                    endColumnNumber: 0,
-                    message: message,
-                    helpKeyword: null,
-                    senderName: null
-            );
-            EnqueueMessage(warningMessageQueue, data, warningDataAvailable);
-        }
-
-        public void LogCustomBuildEvent(CustomBuildEventArgs e)
-        {
-            if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-            {
+			}
+			var data = new BuildWarningEventArgs(
+					subcategory: null,
+					code: code,
+					file: file,
+					lineNumber: lineNumber,
+					columnNumber: 0,
+					endLineNumber: 0,
+					endColumnNumber: 0,
+					message: message,
+					helpKeyword: null,
+					senderName: null
+			);
+			EnqueueMessage(warningMessageQueue, data, warningDataAvailable);
+		}
+
+		public void LogCustomBuildEvent(CustomBuildEventArgs e)
+		{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
+			{
 #pragma warning disable 618
-                BuildEngine.LogCustomEvent(e);
-                return;
+				BuildEngine.LogCustomEvent(e);
+				return;
 #pragma warning restore 618
-            }
-            EnqueueMessage(customMessageQueue, e, customDataAvailable);
-        }
+			}
+			EnqueueMessage(customMessageQueue, e, customDataAvailable);
+		}
 
-        public override bool Execute()
-        {
-            WaitForCompletion();
+		public override bool Execute()
+		{
+			WaitForCompletion();
 #pragma warning disable 618
-            return !Log.HasLoggedErrors;
+			return !Log.HasLoggedErrors;
 #pragma warning restore 618
-        }
-
-        private void EnqueueMessage(Queue queue, object item, ManualResetEvent resetEvent)
-        {
-            lock (queue.SyncRoot)
-            {
-                queue.Enqueue(item);
-                lock (eventlock)
-                {
-                    if (isRunning)
-                        resetEvent.Set();
-                }
-            }
-        }
-
-        private void LogInternal<T>(Queue queue, Action<T> action, ManualResetEvent resetEvent)
-        {
-            lock (queue.SyncRoot)
-            {
-                while (queue.Count > 0)
-                {
-                    var args = (T)queue.Dequeue();
-                    action(args);
-                }
-                resetEvent.Reset();
-            }
-        }
-
-        protected void Yield()
-        {
-            if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
-                ((IBuildEngine3)BuildEngine).Yield();
-        }
-
-        protected void Reacquire()
-        {
-            if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
-                ((IBuildEngine3)BuildEngine).Reacquire();
-        }
-
-        protected void WaitForCompletion()
-        {
-            WaitHandle[] handles = new WaitHandle[] {
-                logDataAvailable,
-                errorDataAvailable,
-                warningDataAvailable,
-                customDataAvailable,
-                telemetryDataAvailable,
-                taskCancelled,
-                completed,
-            };
-            try
-            {
-                while (isRunning)
-                {
-                    var index = (WaitHandleIndex)System.Threading.WaitHandle.WaitAny(handles, TimeSpan.FromMilliseconds(10));
-                    switch (index)
-                    {
-                        case WaitHandleIndex.LogDataAvailable:
-                            LogInternal<BuildMessageEventArgs>(logMessageQueue, (e) =>
-                            {
+		}
+
+		private void EnqueueMessage(Queue queue, object item, ManualResetEvent resetEvent)
+		{
+			lock (queue.SyncRoot)
+			{
+				queue.Enqueue(item);
+				lock (eventlock)
+				{
+					if (isRunning)
+						resetEvent.Set();
+				}
+			}
+		}
+
+		private void LogInternal<T>(Queue queue, Action<T> action, ManualResetEvent resetEvent)
+		{
+			lock (queue.SyncRoot)
+			{
+				while (queue.Count > 0)
+				{
+					var args = (T)queue.Dequeue();
+					action(args);
+				}
+				resetEvent.Reset();
+			}
+		}
+
+		protected void Yield()
+		{
+			if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
+				((IBuildEngine3)BuildEngine).Yield();
+		}
+
+		protected void Reacquire()
+		{
+			if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
+				((IBuildEngine3)BuildEngine).Reacquire();
+		}
+
+		protected void WaitForCompletion()
+		{
+			WaitHandle[] handles = new WaitHandle[] {
+				logDataAvailable,
+				errorDataAvailable,
+				warningDataAvailable,
+				customDataAvailable,
+				telemetryDataAvailable,
+				taskCancelled,
+				completed,
+			};
+			try
+			{
+				while (isRunning)
+				{
+					var index = (WaitHandleIndex)System.Threading.WaitHandle.WaitAny(handles, TimeSpan.FromMilliseconds(10));
+					switch (index)
+					{
+						case WaitHandleIndex.LogDataAvailable:
+							LogInternal<BuildMessageEventArgs>(logMessageQueue, (e) =>
+							{
 #pragma warning disable 618
-                                Log.LogMessage(e.Importance, e.Message);
+								Log.LogMessage(e.Importance, e.Message);
 #pragma warning restore 618
-                            }, logDataAvailable);
-                            break;
-                        case WaitHandleIndex.ErrorDataAvailable:
-                            LogInternal<BuildErrorEventArgs>(errorMessageQueue, (e) =>
-                            {
+							}, logDataAvailable);
+							break;
+						case WaitHandleIndex.ErrorDataAvailable:
+							LogInternal<BuildErrorEventArgs>(errorMessageQueue, (e) =>
+							{
 #pragma warning disable 618
-                                Log.LogError(subcategory: null,
-                                  errorCode: e.Code,
-                                  helpKeyword: null,
-                                  file: e.File,
-                                  lineNumber: e.LineNumber,
-                                  columnNumber: e.ColumnNumber,
-                                  endLineNumber: e.EndLineNumber,
-                                  endColumnNumber: e.EndColumnNumber,
-                                  message: e.Message);
+								Log.LogError(subcategory: null,
+								  errorCode: e.Code,
+								  helpKeyword: null,
+								  file: e.File,
+								  lineNumber: e.LineNumber,
+								  columnNumber: e.ColumnNumber,
+								  endLineNumber: e.EndLineNumber,
+								  endColumnNumber: e.EndColumnNumber,
+								  message: e.Message);
 #pragma warning restore 618
-                            }, errorDataAvailable);
-                            break;
-                        case WaitHandleIndex.WarningDataAvailable:
-                            LogInternal<BuildWarningEventArgs>(warningMessageQueue, (e) =>
-                            {
+							}, errorDataAvailable);
+							break;
+						case WaitHandleIndex.WarningDataAvailable:
+							LogInternal<BuildWarningEventArgs>(warningMessageQueue, (e) =>
+							{
 #pragma warning disable 618
-                                Log.LogWarning(subcategory: null,
-                                  warningCode: e.Code,
-                                  helpKeyword: null,
-                                  file: e.File,
-                                  lineNumber: e.LineNumber,
-                                  columnNumber: e.ColumnNumber,
-                                  endLineNumber: e.EndLineNumber,
-                                  endColumnNumber: e.EndColumnNumber,
-                                  message: e.Message);
+								Log.LogWarning(subcategory: null,
+								  warningCode: e.Code,
+								  helpKeyword: null,
+								  file: e.File,
+								  lineNumber: e.LineNumber,
+								  columnNumber: e.ColumnNumber,
+								  endLineNumber: e.EndLineNumber,
+								  endColumnNumber: e.EndColumnNumber,
+								  message: e.Message);
 #pragma warning restore 618
-                            }, warningDataAvailable);
-                            break;
-                        case WaitHandleIndex.CustomDataAvailable:
-                            LogInternal<CustomBuildEventArgs>(customMessageQueue, (e) =>
-                            {
-                                BuildEngine.LogCustomEvent(e);
-                            }, customDataAvailable);
-                            break;
-                        case WaitHandleIndex.TelemetryDataAvailable:
-                            LogInternal<TelemetryEventArgs>(telemetryMessageQueue, (e) => {
-                                BuildEngine5.LogTelemetry(e.EventName, e.Properties);
-                            }, telemetryDataAvailable);
-                            break;
-                        case WaitHandleIndex.TaskCancelled:
-                            Cancel();
-                            cts.Cancel();
-                            isRunning = false;
-                            break;
-                        case WaitHandleIndex.Completed:
-                            isRunning = false;
-                            break;
-                    }
-                }
-
-            }
-            finally
-            {
-
-            }
-        }
-
-        private enum WaitHandleIndex
-        {
-            LogDataAvailable,
-            ErrorDataAvailable,
-            WarningDataAvailable,
-            CustomDataAvailable,
-            TelemetryDataAvailable,
-            TaskCancelled,
-            Completed,
-        }
-    }
+							}, warningDataAvailable);
+							break;
+						case WaitHandleIndex.CustomDataAvailable:
+							LogInternal<CustomBuildEventArgs>(customMessageQueue, (e) =>
+							{
+								BuildEngine.LogCustomEvent(e);
+							}, customDataAvailable);
+							break;
+						case WaitHandleIndex.TelemetryDataAvailable:
+							LogInternal<TelemetryEventArgs>(telemetryMessageQueue, (e) => {
+								BuildEngine5.LogTelemetry(e.EventName, e.Properties);
+							}, telemetryDataAvailable);
+							break;
+						case WaitHandleIndex.TaskCancelled:
+							Cancel();
+							cts.Cancel();
+							isRunning = false;
+							break;
+						case WaitHandleIndex.Completed:
+							isRunning = false;
+							break;
+					}
+				}
+
+			}
+			finally
+			{
+
+			}
+		}
+
+		private enum WaitHandleIndex
+		{
+			LogDataAvailable,
+			ErrorDataAvailable,
+			WarningDataAvailable,
+			CustomDataAvailable,
+			TelemetryDataAvailable,
+			TaskCancelled,
+			Completed,
+		}
+	}
 }

From f305cf1fc548fd51845ba5084a795094a2d7a58f Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Wed, 12 Jun 2024 16:39:22 -0400
Subject: [PATCH 4/8] Feedback

---
 .../AndroidAsyncTask.cs                       |  32 +-
 .../AsyncTask.cs                              | 395 ++++++++++--------
 .../AsyncTaskExtensionsTests.cs               |  14 +-
 .../AsyncTaskTests.cs                         |  22 +-
 4 files changed, 235 insertions(+), 228 deletions(-)

diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
index f10cb687..4b97af97 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
@@ -1,10 +1,13 @@
 // https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27
 
 using System;
-using static System.Threading.Tasks.TaskExtensions;
 
 namespace Microsoft.Android.Build.Tasks
 {
+	/// <summary>
+	/// AndroidAsyncTask is a thin wrapper around AsyncTask that includes enhanced logging of unhandled exceptions,
+	/// as well as a project directory specific task object key that can be used with the MSBuild RegisterTaskObject API.
+	/// </summary>
 	public abstract class AndroidAsyncTask : AsyncTask
 	{
 		public abstract string TaskPrefix { get; }
@@ -19,33 +22,6 @@ public override bool Execute ()
 			}
 		}
 
-		/// <summary>
-		/// Typically `RunTaskAsync` will be the preferred method to override,
-		///  however this method can be overridden instead for Tasks that will
-		///  run quickly and do not need to be asynchronous.
-		/// </summary>
-		public virtual bool RunTask ()
-		{
-			Yield ();
-			try {
-				this.RunTask (() => RunTaskAsync ())
-					.Unwrap ()
-					.ContinueWith (Complete);
-
-				// This blocks on AsyncTask.Execute, until Complete is called
-				return base.Execute ();
-			} finally {
-				Reacquire ();
-			}
-		}
-
-		/// <summary>
-		/// Override this method for simplicity of AsyncTask usage:
-		/// * Yield / Reacquire is handled for you
-		/// * RunTaskAsync is already on a background thread
-		/// </summary>
-		public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
-
 		protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
 	}
 }
diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
index df518c99..3b61269e 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -7,6 +7,7 @@
 using Microsoft.Build.Utilities;
 using Microsoft.Build.Framework;
 using System.Threading;
+using static System.Threading.Tasks.TaskExtensions;
 using System.Collections;
 using System.Collections.Generic;
 
@@ -16,23 +17,23 @@ namespace Microsoft.Android.Build.Tasks
 	/// Base class for tasks that need long-running cancellable asynchronous tasks 
 	/// that don't block the UI thread in the IDE.
 	/// </summary>
-	public class AsyncTask : Task, ICancelableTask
+	public abstract class AsyncTask : Task, ICancelableTask
 	{
-		readonly CancellationTokenSource cts = new CancellationTokenSource();
-		readonly Queue logMessageQueue = new Queue();
-		readonly Queue warningMessageQueue = new Queue();
-		readonly Queue errorMessageQueue = new Queue();
-		readonly Queue customMessageQueue = new Queue();
+		readonly CancellationTokenSource cts = new CancellationTokenSource ();
+		readonly Queue logMessageQueue = new Queue ();
+		readonly Queue warningMessageQueue = new Queue ();
+		readonly Queue errorMessageQueue = new Queue ();
+		readonly Queue customMessageQueue = new Queue ();
 		readonly Queue telemetryMessageQueue = new Queue ();
-		readonly ManualResetEvent logDataAvailable = new ManualResetEvent(false);
-		readonly ManualResetEvent errorDataAvailable = new ManualResetEvent(false);
-		readonly ManualResetEvent warningDataAvailable = new ManualResetEvent(false);
-		readonly ManualResetEvent customDataAvailable = new ManualResetEvent(false);
-		readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent(false);
-		readonly ManualResetEvent taskCancelled = new ManualResetEvent(false);
-		readonly ManualResetEvent completed = new ManualResetEvent(false);
+		readonly ManualResetEvent logDataAvailable = new ManualResetEvent (false);
+		readonly ManualResetEvent errorDataAvailable = new ManualResetEvent (false);
+		readonly ManualResetEvent warningDataAvailable = new ManualResetEvent (false);
+		readonly ManualResetEvent customDataAvailable = new ManualResetEvent (false);
+		readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent (false);
+		readonly ManualResetEvent taskCancelled = new ManualResetEvent (false);
+		readonly ManualResetEvent completed = new ManualResetEvent (false);
 		bool isRunning = true;
-		object eventlock = new object();
+		object eventlock = new object ();
 		int uiThreadId = 0;
 
 		/// <summary>
@@ -51,169 +52,171 @@ public class AsyncTask : Task, ICancelableTask
 		/// </summary>
 		protected string WorkingDirectory { get; private set; }
 
-		[Obsolete("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")]
+		[Obsolete ("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")]
 		private new TaskLoggingHelper Log => base.Log;
 
 		/// <summary>
 		/// Initializes the task.
 		/// </summary>
-		public AsyncTask()
+		protected AsyncTask ()
 		{
 			YieldDuringToolExecution = false;
-			WorkingDirectory = Directory.GetCurrentDirectory();
+			WorkingDirectory = Directory.GetCurrentDirectory ();
 			uiThreadId = Thread.CurrentThread.ManagedThreadId;
 		}
 
-		public void Cancel() => taskCancelled.Set();
+		public void Cancel ()
+			=> taskCancelled.Set ();
 
-		protected void Complete(System.Threading.Tasks.Task task)
+		protected void Complete (System.Threading.Tasks.Task task)
 		{
-			if (task.Exception != null)
-			{
-				var ex = task.Exception.GetBaseException();
-				LogError(ex.Message + Environment.NewLine + ex.StackTrace);
+			if (task.Exception != null) {
+				var ex = task.Exception.GetBaseException ();
+				LogError (ex.Message + Environment.NewLine + ex.StackTrace);
 			}
-			Complete();
+			Complete ();
 		}
 
-		public void Complete() => completed.Set();
+		public void Complete ()
+			=> completed.Set ();
 
-		public void LogDebugTaskItems(string message, string[] items)
+		public void LogDebugTaskItems (string message, string [] items)
 		{
-			LogDebugMessage(message);
+			LogDebugMessage (message);
 
 			if (items == null)
 				return;
 
 			foreach (var item in items)
-				LogDebugMessage("	{0}", item);
+				LogDebugMessage ("	{0}", item);
 		}
 
-		public void LogDebugTaskItems(string message, ITaskItem[] items)
+		public void LogDebugTaskItems (string message, ITaskItem [] items)
 		{
-			LogDebugMessage(message);
+			LogDebugMessage (message);
 
 			if (items == null)
 				return;
 
 			foreach (var item in items)
-				LogDebugMessage("	{0}", item.ItemSpec);
+				LogDebugMessage ("	{0}", item.ItemSpec);
 		}
 
-		public void LogTelemetry(string eventName, IDictionary<string, string> properties)
+		public void LogTelemetry (string eventName, IDictionary<string, string> properties)
 		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-			{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
 #pragma warning disable 618
-				Log.LogTelemetry(eventName, properties);
+				Log.LogTelemetry (eventName, properties);
 				return;
 #pragma warning restore 618
 			}
 
-			var data = new TelemetryEventArgs() {
-					EventName = eventName,
-					Properties = properties
+			var data = new TelemetryEventArgs () {
+				EventName = eventName,
+				Properties = properties
 			};
-			EnqueueMessage(telemetryMessageQueue, data, telemetryDataAvailable);
+			EnqueueMessage (telemetryMessageQueue, data, telemetryDataAvailable);
 		}
 
-		public void LogMessage(string message) => LogMessage(message, importance: MessageImportance.Normal);
+		public void LogMessage (string message)
+			=> LogMessage (message, importance: MessageImportance.Normal);
 
-		public void LogMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs));
+		public void LogMessage (string message, params object [] messageArgs)
+			=> LogMessage (string.Format (message, messageArgs));
 
-		public void LogDebugMessage(string message) => LogMessage(message, importance: MessageImportance.Low);
+		public void LogDebugMessage (string message)
+			=> LogMessage (message, importance: MessageImportance.Low);
 
-		public void LogDebugMessage(string message, params object[] messageArgs) => LogMessage(string.Format(message, messageArgs), importance: MessageImportance.Low);
+		public void LogDebugMessage (string message, params object [] messageArgs)
+			=> LogMessage (string.Format (message, messageArgs), importance: MessageImportance.Low);
 
-		public void LogMessage(string message, MessageImportance importance = MessageImportance.Normal)
+		public void LogMessage (string message, MessageImportance importance = MessageImportance.Normal)
 		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-			{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
 #pragma warning disable 618
-				Log.LogMessage(importance, message);
+				Log.LogMessage (importance, message);
 				return;
 #pragma warning restore 618
 			}
 
-			var data = new BuildMessageEventArgs(
+			var data = new BuildMessageEventArgs (
 					message: message,
 					helpKeyword: null,
 					senderName: null,
 					importance: importance
 			);
-			EnqueueMessage(logMessageQueue, data, logDataAvailable);
+			EnqueueMessage (logMessageQueue, data, logDataAvailable);
 		}
 
-		public void LogError(string message) => LogCodedError(code: null, message: message, file: null, lineNumber: 0);
+		public void LogError (string message)
+			=> LogCodedError (code: null, message: message, file: null, lineNumber: 0);
 
-		public void LogError(string message, params object[] messageArgs) => LogCodedError(code: null, message: string.Format(message, messageArgs));
+		public void LogError (string message, params object [] messageArgs)
+			=> LogCodedError (code: null, message: string.Format (message, messageArgs));
 
-		public void LogErrorFromException(Exception exception)
+		public void LogErrorFromException (Exception exception)
 		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-			{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
 #pragma warning disable 618
-				Log.LogErrorFromException(exception);
+				Log.LogErrorFromException (exception);
 				return;
 #pragma warning restore 618
 			}
 
 			StackFrame exceptionFrame = null;
-			try
-			{
-				exceptionFrame = new StackTrace(exception, true)?.GetFrames()?.FirstOrDefault();
+			if (exception != null) {
+				exceptionFrame = new StackTrace (exception, true)?.GetFrames ()?.FirstOrDefault ();
 			}
-			catch { }
 
-			lock (errorMessageQueue.SyncRoot)
-			{
-				errorMessageQueue.Enqueue(new BuildErrorEventArgs(
-					subcategory: null,
-					code: null,
-					file: exceptionFrame?.GetFileName(),
-					lineNumber: exceptionFrame?.GetFileLineNumber() ?? 0,
-					columnNumber: exceptionFrame?.GetFileColumnNumber() ?? 0,
-					endLineNumber: 0,
-					endColumnNumber: 0,
-					message: exception.Message,
-					helpKeyword: null,
-					senderName: null
+			lock (errorMessageQueue.SyncRoot) {
+				errorMessageQueue.Enqueue (new BuildErrorEventArgs (
+						subcategory: null,
+						code: null,
+						file: exceptionFrame?.GetFileName (),
+						lineNumber: exceptionFrame?.GetFileLineNumber () ?? 0,
+						columnNumber: exceptionFrame?.GetFileColumnNumber () ?? 0,
+						endLineNumber: 0,
+						endColumnNumber: 0,
+						message: exception.Message,
+						helpKeyword: null,
+						senderName: null
 				));
-				lock (eventlock)
-				{
+				lock (eventlock) {
 					if (isRunning)
-						errorDataAvailable.Set();
+						errorDataAvailable.Set ();
 				}
 			}
 		}
 
-		public void LogCodedError(string code, string message) => LogCodedError(code: code, message: message, file: null, lineNumber: 0);
+		public void LogCodedError (string code, string message)
+			=> LogCodedError (code: code, message: message, file: null, lineNumber: 0);
 
-		public void LogCodedError(string code, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
+		public void LogCodedError (string code, string message, params object [] messageArgs)
+			=> LogCodedError (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0);
 
-		public void LogCodedError(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedError(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
+		public void LogCodedError (string code, string file, int lineNumber, string message, params object [] messageArgs)
+			=> LogCodedError (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber);
 
-		public void LogCodedError(string code, string message, string file, int lineNumber)
+		public void LogCodedError (string code, string message, string file, int lineNumber)
 		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-			{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
 #pragma warning disable 618
-				Log.LogError(
-					subcategory: null,
-					errorCode: code,
-					helpKeyword: null,
-					file: file,
-					lineNumber: lineNumber,
-					columnNumber: 0,
-					endLineNumber: 0,
-					endColumnNumber: 0,
-					message: message
+				Log.LogError (
+						subcategory: null,
+						errorCode: code,
+						helpKeyword: null,
+						file: file,
+						lineNumber: lineNumber,
+						columnNumber: 0,
+						endLineNumber: 0,
+						endColumnNumber: 0,
+						message: message
 				);
 				return;
 #pragma warning restore 618
 			}
 
-			var data = new BuildErrorEventArgs(
+			var data = new BuildErrorEventArgs (
 					subcategory: null,
 					code: code,
 					file: file,
@@ -225,39 +228,43 @@ public void LogCodedError(string code, string message, string file, int lineNumb
 					helpKeyword: null,
 					senderName: null
 			);
-			EnqueueMessage(errorMessageQueue, data, errorDataAvailable);
+			EnqueueMessage (errorMessageQueue, data, errorDataAvailable);
 		}
 
-		public void LogWarning(string message) => LogCodedWarning(code: null, message: message, file: null, lineNumber: 0);
+		public void LogWarning (string message)
+			=> LogCodedWarning (code: null, message: message, file: null, lineNumber: 0);
 
-		public void LogWarning(string message, params object[] messageArgs) => LogCodedWarning(code: null, message: string.Format(message, messageArgs));
+		public void LogWarning (string message, params object [] messageArgs)
+			=> LogCodedWarning (code: null, message: string.Format (message, messageArgs));
 
-		public void LogCodedWarning(string code, string message) => LogCodedWarning(code: code, message: message, file: null, lineNumber: 0);
+		public void LogCodedWarning (string code, string message)
+			=> LogCodedWarning (code: code, message: message, file: null, lineNumber: 0);
 
-		public void LogCodedWarning(string code, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: null, lineNumber: 0);
+		public void LogCodedWarning (string code, string message, params object [] messageArgs)
+			=> LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0);
 
-		public void LogCodedWarning(string code, string file, int lineNumber, string message, params object[] messageArgs) => LogCodedWarning(code: code, message: string.Format(message, messageArgs), file: file, lineNumber: lineNumber);
+		public void LogCodedWarning (string code, string file, int lineNumber, string message, params object [] messageArgs)
+			=> LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber);
 
-		public void LogCodedWarning(string code, string message, string file, int lineNumber)
+		public void LogCodedWarning (string code, string message, string file, int lineNumber)
 		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-			{
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
 #pragma warning disable 618
-				Log.LogWarning(
-					subcategory: null,
-					warningCode: code,
-					helpKeyword: null,
-					file: file,
-					lineNumber: lineNumber,
-					columnNumber: 0,
-					endLineNumber: 0,
-					endColumnNumber: 0,
-					message: message
+				Log.LogWarning (
+						subcategory: null,
+						warningCode: code,
+						helpKeyword: null,
+						file: file,
+						lineNumber: lineNumber,
+						columnNumber: 0,
+						endLineNumber: 0,
+						endColumnNumber: 0,
+						message: message
 				);
 				return;
 #pragma warning restore 618
 			}
-			var data = new BuildWarningEventArgs(
+			var data = new BuildWarningEventArgs (
 					subcategory: null,
 					code: code,
 					file: file,
@@ -269,70 +276,96 @@ public void LogCodedWarning(string code, string message, string file, int lineNu
 					helpKeyword: null,
 					senderName: null
 			);
-			EnqueueMessage(warningMessageQueue, data, warningDataAvailable);
+			EnqueueMessage (warningMessageQueue, data, warningDataAvailable);
 		}
 
-		public void LogCustomBuildEvent(CustomBuildEventArgs e)
+		public void LogCustomBuildEvent (CustomBuildEventArgs e)
 		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId)
-			{
-#pragma warning disable 618
-				BuildEngine.LogCustomEvent(e);
+			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
+				BuildEngine.LogCustomEvent (e);
 				return;
-#pragma warning restore 618
 			}
-			EnqueueMessage(customMessageQueue, e, customDataAvailable);
+			EnqueueMessage (customMessageQueue, e, customDataAvailable);
 		}
 
-		public override bool Execute()
+		public override bool Execute ()
 		{
-			WaitForCompletion();
+			WaitForCompletion ();
 #pragma warning disable 618
 			return !Log.HasLoggedErrors;
 #pragma warning restore 618
 		}
 
-		private void EnqueueMessage(Queue queue, object item, ManualResetEvent resetEvent)
+		/// <summary>
+		/// Typically `RunTaskAsync` will be the preferred method to override,
+		///  however this method can be overridden instead for Tasks that will
+		///  run quickly and do not need to be asynchronous.
+		/// </summary>
+		public virtual bool RunTask ()
 		{
-			lock (queue.SyncRoot)
-			{
-				queue.Enqueue(item);
-				lock (eventlock)
-				{
+			Yield ();
+			try {
+				this.RunTask (() => RunTaskAsync ())
+					.Unwrap ()
+					.ContinueWith (Complete);
+
+				// This blocks on Execute, until Complete is called
+				return Execute ();
+			} finally {
+				Reacquire ();
+			}
+		}
+
+		/// <summary>
+		/// Override this method for simplicity of AsyncTask usage:
+		/// <list type="bullet">
+		/// <item>
+		/// <description>Yield / Reacquire is handled for you</description>
+		/// </item>
+		/// <item>
+		/// <description>RunTaskAsync is already on a background thread</description>
+		/// </item>
+		/// </list>
+		/// </summary>
+		public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
+
+		void EnqueueMessage (Queue queue, object item, ManualResetEvent resetEvent)
+		{
+			lock (queue.SyncRoot) {
+				queue.Enqueue (item);
+				lock (eventlock) {
 					if (isRunning)
-						resetEvent.Set();
+						resetEvent.Set ();
 				}
 			}
 		}
 
-		private void LogInternal<T>(Queue queue, Action<T> action, ManualResetEvent resetEvent)
+		void LogInternal<T> (Queue queue, Action<T> action, ManualResetEvent resetEvent)
 		{
-			lock (queue.SyncRoot)
-			{
-				while (queue.Count > 0)
-				{
-					var args = (T)queue.Dequeue();
-					action(args);
+			lock (queue.SyncRoot) {
+				while (queue.Count > 0) {
+					var args = (T) queue.Dequeue ();
+					action (args);
 				}
-				resetEvent.Reset();
+				resetEvent.Reset ();
 			}
 		}
 
-		protected void Yield()
+		protected void Yield ()
 		{
 			if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
-				((IBuildEngine3)BuildEngine).Yield();
+				((IBuildEngine3) BuildEngine).Yield ();
 		}
 
-		protected void Reacquire()
+		protected void Reacquire ()
 		{
 			if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
-				((IBuildEngine3)BuildEngine).Reacquire();
+				((IBuildEngine3) BuildEngine).Reacquire ();
 		}
 
-		protected void WaitForCompletion()
+		protected void WaitForCompletion ()
 		{
-			WaitHandle[] handles = new WaitHandle[] {
+			WaitHandle [] handles = new WaitHandle [] {
 				logDataAvailable,
 				errorDataAvailable,
 				warningDataAvailable,
@@ -341,67 +374,61 @@ protected void WaitForCompletion()
 				taskCancelled,
 				completed,
 			};
-			try
-			{
-				while (isRunning)
-				{
-					var index = (WaitHandleIndex)System.Threading.WaitHandle.WaitAny(handles, TimeSpan.FromMilliseconds(10));
-					switch (index)
-					{
+			try {
+				while (isRunning) {
+					var index = (WaitHandleIndex) System.Threading.WaitHandle.WaitAny (handles, TimeSpan.FromMilliseconds (10));
+					switch (index) {
 						case WaitHandleIndex.LogDataAvailable:
-							LogInternal<BuildMessageEventArgs>(logMessageQueue, (e) =>
-							{
+							LogInternal<BuildMessageEventArgs> (logMessageQueue, (e) => {
 #pragma warning disable 618
-								Log.LogMessage(e.Importance, e.Message);
+								Log.LogMessage (e.Importance, e.Message);
 #pragma warning restore 618
 							}, logDataAvailable);
 							break;
 						case WaitHandleIndex.ErrorDataAvailable:
-							LogInternal<BuildErrorEventArgs>(errorMessageQueue, (e) =>
-							{
+							LogInternal<BuildErrorEventArgs> (errorMessageQueue, (e) => {
 #pragma warning disable 618
-								Log.LogError(subcategory: null,
-								  errorCode: e.Code,
-								  helpKeyword: null,
-								  file: e.File,
-								  lineNumber: e.LineNumber,
-								  columnNumber: e.ColumnNumber,
-								  endLineNumber: e.EndLineNumber,
-								  endColumnNumber: e.EndColumnNumber,
-								  message: e.Message);
+								Log.LogError (
+										subcategory: null,
+										errorCode: e.Code,
+										helpKeyword: null,
+										file: e.File,
+										lineNumber: e.LineNumber,
+										columnNumber: e.ColumnNumber,
+										endLineNumber: e.EndLineNumber,
+										endColumnNumber: e.EndColumnNumber,
+										message: e.Message);
 #pragma warning restore 618
 							}, errorDataAvailable);
 							break;
 						case WaitHandleIndex.WarningDataAvailable:
-							LogInternal<BuildWarningEventArgs>(warningMessageQueue, (e) =>
-							{
+							LogInternal<BuildWarningEventArgs> (warningMessageQueue, (e) => {
 #pragma warning disable 618
-								Log.LogWarning(subcategory: null,
-								  warningCode: e.Code,
-								  helpKeyword: null,
-								  file: e.File,
-								  lineNumber: e.LineNumber,
-								  columnNumber: e.ColumnNumber,
-								  endLineNumber: e.EndLineNumber,
-								  endColumnNumber: e.EndColumnNumber,
-								  message: e.Message);
+								Log.LogWarning (subcategory: null,
+										warningCode: e.Code,
+										helpKeyword: null,
+										file: e.File,
+										lineNumber: e.LineNumber,
+										columnNumber: e.ColumnNumber,
+										endLineNumber: e.EndLineNumber,
+										endColumnNumber: e.EndColumnNumber,
+										message: e.Message);
 #pragma warning restore 618
 							}, warningDataAvailable);
 							break;
 						case WaitHandleIndex.CustomDataAvailable:
-							LogInternal<CustomBuildEventArgs>(customMessageQueue, (e) =>
-							{
-								BuildEngine.LogCustomEvent(e);
+							LogInternal<CustomBuildEventArgs> (customMessageQueue, (e) => {
+								BuildEngine.LogCustomEvent (e);
 							}, customDataAvailable);
 							break;
 						case WaitHandleIndex.TelemetryDataAvailable:
-							LogInternal<TelemetryEventArgs>(telemetryMessageQueue, (e) => {
-								BuildEngine5.LogTelemetry(e.EventName, e.Properties);
+							LogInternal<TelemetryEventArgs> (telemetryMessageQueue, (e) => {
+								BuildEngine5.LogTelemetry (e.EventName, e.Properties);
 							}, telemetryDataAvailable);
 							break;
 						case WaitHandleIndex.TaskCancelled:
-							Cancel();
-							cts.Cancel();
+							Cancel ();
+							cts.Cancel ();
 							isRunning = false;
 							break;
 						case WaitHandleIndex.Completed:
@@ -410,9 +437,7 @@ protected void WaitForCompletion()
 					}
 				}
 
-			}
-			finally
-			{
+			} finally {
 
 			}
 		}
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
index 6f9d3c30..9c8e6b70 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
@@ -10,11 +10,13 @@ public class AsyncTaskExtensionsTests
 	{
 		const int Iterations = 32;
 
+		class TestAsyncTask : AsyncTask { }
+
 		[Test]
 		public async Task RunTask ()
 		{
 			bool set = false;
-			await new AsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type
+			await new TestAsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type
 			Assert.IsTrue (set);
 		}
 
@@ -22,7 +24,7 @@ public async Task RunTask ()
 		public async Task RunTaskOfT ()
 		{
 			bool set = false;
-			Assert.IsTrue (await new AsyncTask ().RunTask (() => set = true), "RunTask should return true");
+			Assert.IsTrue (await new TestAsyncTask ().RunTask (() => set = true), "RunTask should return true");
 			Assert.IsTrue (set);
 		}
 
@@ -30,7 +32,7 @@ public async Task RunTaskOfT ()
 		public async Task WhenAll ()
 		{
 			bool set = false;
-			await new AsyncTask ().WhenAll (new [] { 0 }, _ => set = true);
+			await new TestAsyncTask ().WhenAll (new [] { 0 }, _ => set = true);
 			Assert.IsTrue (set);
 		}
 
@@ -39,7 +41,7 @@ public async Task WhenAllWithLock ()
 		{
 			var input = new int [Iterations];
 			var output = new List<int> ();
-			await new AsyncTask ().WhenAllWithLock (input, (i, l) => {
+			await new TestAsyncTask ().WhenAllWithLock (input, (i, l) => {
 				lock (l) output.Add (i);
 			});
 			Assert.AreEqual (Iterations, output.Count);
@@ -49,7 +51,7 @@ public async Task WhenAllWithLock ()
 		public void ParallelForEach ()
 		{
 			bool set = false;
-			new AsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true);
+			new TestAsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true);
 			Assert.IsTrue (set);
 		}
 
@@ -58,7 +60,7 @@ public void ParallelForEachWithLock ()
 		{
 			var input = new int [Iterations];
 			var output = new List<int> ();
-			new AsyncTask ().ParallelForEachWithLock (input, (i, l) => {
+			new TestAsyncTask ().ParallelForEachWithLock (input, (i, l) => {
 				lock (l) output.Add (i);
 			});
 			Assert.AreEqual (Iterations, output.Count);
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
index e4dc75a8..e91ad15c 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
@@ -17,7 +17,7 @@ public class AsyncTaskTests
 		MockBuildEngine engine;
 
 		[SetUp]
-		public void TestSetup()
+		public void TestSetup ()
 		{
 			errors = new List<BuildErrorEventArgs> ();
 			warnings = new List<BuildWarningEventArgs> ();
@@ -29,18 +29,22 @@ public class AsyncMessage : AsyncTask
 		{
 			public string Text { get; set; }
 
-			public override bool Execute()
+			public override bool Execute ()
 			{
-				Task.Run(async () =>
-				{
-					await Task.Delay(5000);
-					LogMessage(Text);
-					Complete();
+				Task.Run (async () => {
+					await Task.Delay (5000);
+					LogMessage (Text);
+					Complete ();
 				});
 
-				LogTelemetry("Test", new System.Collections.Generic.Dictionary<string, string> () {{"Property", "Value"}});
+				LogTelemetry ("Test", new Dictionary<string, string> () { { "Property", "Value" } });
 
-				return base.Execute();
+				return base.Execute ();
+			}
+
+			public override Task RunTaskAsync ()
+			{
+				return base.RunTaskAsync ();
 			}
 		}
 

From 68db73b16c4d1051e7e1cc22638f220d52100397 Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Wed, 12 Jun 2024 18:37:06 -0400
Subject: [PATCH 5/8] Fix SO

---
 .../AsyncTask.cs                              |  9 ++++++--
 .../AsyncTaskTests.cs                         | 21 ++++++++++++++-----
 2 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
index 3b61269e..2e973f0b 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -288,7 +288,7 @@ public void LogCustomBuildEvent (CustomBuildEventArgs e)
 			EnqueueMessage (customMessageQueue, e, customDataAvailable);
 		}
 
-		public override bool Execute ()
+		bool ExecuteWaitForCompletion ()
 		{
 			WaitForCompletion ();
 #pragma warning disable 618
@@ -296,6 +296,11 @@ public override bool Execute ()
 #pragma warning restore 618
 		}
 
+		public override bool Execute ()
+		{
+			return ExecuteWaitForCompletion ();
+		}
+
 		/// <summary>
 		/// Typically `RunTaskAsync` will be the preferred method to override,
 		///  however this method can be overridden instead for Tasks that will
@@ -310,7 +315,7 @@ public virtual bool RunTask ()
 					.ContinueWith (Complete);
 
 				// This blocks on Execute, until Complete is called
-				return Execute ();
+				return ExecuteWaitForCompletion ();
 			} finally {
 				Reacquire ();
 			}
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
index e91ad15c..d9596436 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
@@ -41,11 +41,6 @@ public override bool Execute ()
 
 				return base.Execute ();
 			}
-
-			public override Task RunTaskAsync ()
-			{
-				return base.RunTaskAsync ();
-			}
 		}
 
 		[Test]
@@ -61,5 +56,21 @@ public void RunAsyncMessage ()
 				$"Task did not contain expected message text: '{message}'.");
 		}
 
+
+		public class TestAAT : AndroidAsyncTask
+		{
+			public override string TaskPrefix => "TEST";
+		}
+
+		[Test]
+		public void AndroidAsyncTaskWithRunOverride ()
+		{
+			var task = new TestAAT () {
+				BuildEngine = engine,
+			};
+
+			Assert.IsTrue (task.Execute (), "Empty AndroidAsyncTask should have ran successfully.");
+		}
+
 	}
 }

From 04b287331c7cfe11c2cbd6530235b4c5b7597865 Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Mon, 24 Jun 2024 18:28:31 -0400
Subject: [PATCH 6/8] Merge AndroidAsyncTask -> AsyncTask

---
 .../AndroidAsyncTask.cs                       | 27 ------
 .../AsyncTask.cs                              | 88 +++++++++++--------
 .../AsyncTaskExtensionsTests.cs               |  5 +-
 .../AsyncTaskTests.cs                         |  8 +-
 4 files changed, 58 insertions(+), 70 deletions(-)
 delete mode 100644 src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs

diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
deleted file mode 100644
index 4b97af97..00000000
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27
-
-using System;
-
-namespace Microsoft.Android.Build.Tasks
-{
-	/// <summary>
-	/// AndroidAsyncTask is a thin wrapper around AsyncTask that includes enhanced logging of unhandled exceptions,
-	/// as well as a project directory specific task object key that can be used with the MSBuild RegisterTaskObject API.
-	/// </summary>
-	public abstract class AndroidAsyncTask : AsyncTask
-	{
-		public abstract string TaskPrefix { get; }
-
-		public override bool Execute ()
-		{
-			try {
-				return RunTask ();
-			} catch (Exception ex) {
-				this.LogUnhandledException (TaskPrefix, ex);
-				return false;
-			}
-		}
-
-		protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
-	}
-}
diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
index 2e973f0b..6430049b 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -1,3 +1,4 @@
+// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27
 // https://github.com/xamarin/Xamarin.Build.AsyncTask/blob/db4ce14dacfef47435c238b1b681c124e60ea1a0/Xamarin.Build.AsyncTask/AsyncTask.cs
 
 using System;
@@ -14,11 +15,13 @@
 namespace Microsoft.Android.Build.Tasks
 {
 	/// <summary>
-	/// Base class for tasks that need long-running cancellable asynchronous tasks 
+	/// Base class for tasks that need long-running cancellable asynchronous tasks
 	/// that don't block the UI thread in the IDE.
 	/// </summary>
 	public abstract class AsyncTask : Task, ICancelableTask
 	{
+		public abstract string TaskPrefix { get; }
+
 		readonly CancellationTokenSource cts = new CancellationTokenSource ();
 		readonly Queue logMessageQueue = new Queue ();
 		readonly Queue warningMessageQueue = new Queue ();
@@ -296,44 +299,6 @@ bool ExecuteWaitForCompletion ()
 #pragma warning restore 618
 		}
 
-		public override bool Execute ()
-		{
-			return ExecuteWaitForCompletion ();
-		}
-
-		/// <summary>
-		/// Typically `RunTaskAsync` will be the preferred method to override,
-		///  however this method can be overridden instead for Tasks that will
-		///  run quickly and do not need to be asynchronous.
-		/// </summary>
-		public virtual bool RunTask ()
-		{
-			Yield ();
-			try {
-				this.RunTask (() => RunTaskAsync ())
-					.Unwrap ()
-					.ContinueWith (Complete);
-
-				// This blocks on Execute, until Complete is called
-				return ExecuteWaitForCompletion ();
-			} finally {
-				Reacquire ();
-			}
-		}
-
-		/// <summary>
-		/// Override this method for simplicity of AsyncTask usage:
-		/// <list type="bullet">
-		/// <item>
-		/// <description>Yield / Reacquire is handled for you</description>
-		/// </item>
-		/// <item>
-		/// <description>RunTaskAsync is already on a background thread</description>
-		/// </item>
-		/// </list>
-		/// </summary>
-		public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
-
 		void EnqueueMessage (Queue queue, object item, ManualResetEvent resetEvent)
 		{
 			lock (queue.SyncRoot) {
@@ -447,6 +412,51 @@ protected void WaitForCompletion ()
 			}
 		}
 
+		public override bool Execute ()
+		{
+			try {
+				return RunTask ();
+			} catch (Exception ex) {
+				this.LogUnhandledException (TaskPrefix, ex);
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Typically `RunTaskAsync` will be the preferred method to override,
+		///  however this method can be overridden instead for Tasks that will
+		///  run quickly and do not need to be asynchronous.
+		/// </summary>
+		public virtual bool RunTask ()
+		{
+			Yield ();
+			try {
+				this.RunTask (() => RunTaskAsync ())
+					.Unwrap ()
+					.ContinueWith (Complete);
+
+				// This blocks on Execute, until Complete is called
+				return ExecuteWaitForCompletion ();
+			} finally {
+				Reacquire ();
+			}
+		}
+
+		/// <summary>
+		/// Override this method for simplicity of AsyncTask usage:
+		/// <list type="bullet">
+		/// <item>
+		/// <description>Yield / Reacquire is handled for you</description>
+		/// </item>
+		/// <item>
+		/// <description>RunTaskAsync is already on a background thread</description>
+		/// </item>
+		/// </list>
+		/// </summary>
+		public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
+
+		protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
+
 		private enum WaitHandleIndex
 		{
 			LogDataAvailable,
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
index 9c8e6b70..13a9762d 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
@@ -10,7 +10,10 @@ public class AsyncTaskExtensionsTests
 	{
 		const int Iterations = 32;
 
-		class TestAsyncTask : AsyncTask { }
+		class TestAsyncTask : AsyncTask
+		{
+			public override string TaskPrefix => "TEST";
+		}
 
 		[Test]
 		public async Task RunTask ()
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
index d9596436..05014d18 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
@@ -27,6 +27,8 @@ public void TestSetup ()
 
 		public class AsyncMessage : AsyncTask
 		{
+			public override string TaskPrefix => "TEST";
+
 			public string Text { get; set; }
 
 			public override bool Execute ()
@@ -44,7 +46,7 @@ public override bool Execute ()
 		}
 
 		[Test]
-		public void RunAsyncMessage ()
+		public void RunAsyncMessageExecOverride ()
 		{
 			var message = "Hello Async World!";
 			var task = new AsyncMessage () {
@@ -57,13 +59,13 @@ public void RunAsyncMessage ()
 		}
 
 
-		public class TestAAT : AndroidAsyncTask
+		class TestAAT : AsyncTask
 		{
 			public override string TaskPrefix => "TEST";
 		}
 
 		[Test]
-		public void AndroidAsyncTaskWithRunOverride ()
+		public void RunAndroidAsyncTask ()
 		{
 			var task = new TestAAT () {
 				BuildEngine = engine,

From ffc4ed41883ef03ed5c0cf1a02969abb070e273d Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Tue, 25 Jun 2024 16:27:11 -0400
Subject: [PATCH 7/8] Remove custom exception logging, use
 LogUnhandledException

---
 .../AsyncTask.cs                              | 36 +---------
 .../AsyncTaskTests.cs                         | 65 +++++++++++++------
 ...osoft.Android.Build.BaseTasks-Tests.csproj |  2 +-
 3 files changed, 48 insertions(+), 55 deletions(-)

diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
index 6430049b..7a8a6072 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -75,7 +75,7 @@ protected void Complete (System.Threading.Tasks.Task task)
 		{
 			if (task.Exception != null) {
 				var ex = task.Exception.GetBaseException ();
-				LogError (ex.Message + Environment.NewLine + ex.StackTrace);
+				this.LogUnhandledException (TaskPrefix, ex);
 			}
 			Complete ();
 		}
@@ -157,40 +157,6 @@ public void LogError (string message)
 		public void LogError (string message, params object [] messageArgs)
 			=> LogCodedError (code: null, message: string.Format (message, messageArgs));
 
-		public void LogErrorFromException (Exception exception)
-		{
-			if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
-#pragma warning disable 618
-				Log.LogErrorFromException (exception);
-				return;
-#pragma warning restore 618
-			}
-
-			StackFrame exceptionFrame = null;
-			if (exception != null) {
-				exceptionFrame = new StackTrace (exception, true)?.GetFrames ()?.FirstOrDefault ();
-			}
-
-			lock (errorMessageQueue.SyncRoot) {
-				errorMessageQueue.Enqueue (new BuildErrorEventArgs (
-						subcategory: null,
-						code: null,
-						file: exceptionFrame?.GetFileName (),
-						lineNumber: exceptionFrame?.GetFileLineNumber () ?? 0,
-						columnNumber: exceptionFrame?.GetFileColumnNumber () ?? 0,
-						endLineNumber: 0,
-						endColumnNumber: 0,
-						message: exception.Message,
-						helpKeyword: null,
-						senderName: null
-				));
-				lock (eventlock) {
-					if (isRunning)
-						errorDataAvailable.Set ();
-				}
-			}
-		}
-
 		public void LogCodedError (string code, string message)
 			=> LogCodedError (code: code, message: message, file: null, lineNumber: 0);
 
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
index 05014d18..912b4c1a 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
@@ -25,53 +25,80 @@ public void TestSetup ()
 			engine = new MockBuildEngine (TestContext.Out, errors, warnings, messages);
 		}
 
+		class AsyncTaskTest : AsyncTask
+		{
+			public override string TaskPrefix => "ATT";
+		}
+
 		public class AsyncMessage : AsyncTask
 		{
-			public override string TaskPrefix => "TEST";
+			public override string TaskPrefix => "AM";
 
 			public string Text { get; set; }
 
 			public override bool Execute ()
 			{
-				Task.Run (async () => {
-					await Task.Delay (5000);
-					LogMessage (Text);
-					Complete ();
-				});
-
 				LogTelemetry ("Test", new Dictionary<string, string> () { { "Property", "Value" } });
-
 				return base.Execute ();
 			}
+
+			public override async Task RunTaskAsync ()
+			{
+				await Task.Delay (5000);
+				LogMessage (Text);
+				Complete ();
+			}
 		}
 
+		class AsyncTaskExceptionTest : AsyncTask
+		{
+			public override string TaskPrefix => "ATET";
+
+			public string ExceptionMessage { get; set; }
+
+			public override Task RunTaskAsync ()
+			{
+				throw new System.InvalidOperationException (ExceptionMessage);
+			}
+		}
+
+
 		[Test]
-		public void RunAsyncMessageExecOverride ()
+		public void RunAsyncTask ()
+		{
+			var task = new AsyncTaskTest () {
+				BuildEngine = engine,
+			};
+
+			Assert.IsTrue (task.Execute (), "Empty AsyncTask should have ran successfully.");
+		}
+
+		[Test]
+		public void RunAsyncTaskOverride ()
 		{
 			var message = "Hello Async World!";
 			var task = new AsyncMessage () {
 				BuildEngine = engine,
-				Text = message
+				Text = message,
 			};
 			var taskSucceeded = task.Execute ();
 			Assert.IsTrue (messages.Any (e => e.Message.Contains (message)),
 				$"Task did not contain expected message text: '{message}'.");
 		}
 
-
-		class TestAAT : AsyncTask
-		{
-			public override string TaskPrefix => "TEST";
-		}
-
 		[Test]
-		public void RunAndroidAsyncTask ()
+		public void RunAsyncTaskExpectedException ()
 		{
-			var task = new TestAAT () {
+			var expectedException = "test exception!";
+			var task = new AsyncTaskExceptionTest () {
 				BuildEngine = engine,
+				ExceptionMessage = expectedException,
 			};
 
-			Assert.IsTrue (task.Execute (), "Empty AndroidAsyncTask should have ran successfully.");
+			Assert.IsFalse (task.Execute (), "Exception AsyncTask should have failed.");
+			Assert.IsTrue (errors.Count == 1, "Exception AsyncTask should have produced one error.");
+			Assert.IsTrue (errors[0].Message.Contains (expectedException),
+				$"Task did not contain expected error text: '{expectedException}'.");
 		}
 
 	}
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
index 7f0fb6d7..99bdc267 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
@@ -4,7 +4,7 @@
     <!-- $(TargetFrameworks) is used to allow the $(TargetFramework) check in Directory.Build.props to work.
         If $(TargetFramework) is declared here instead, it will not be evaluated before Directory.Build.props
         is loaded and the wrong $(TestOutputFullPath) will be used. -->
-    <TargetFrameworks>net6.0</TargetFrameworks>
+    <TargetFrameworks>net8.0</TargetFrameworks>
     <RootNamespace>Microsoft.Android.Build.BaseTasks.Tests</RootNamespace>
     <IsPackable>false</IsPackable>
     <OutputPath>$(TestOutputFullPath)</OutputPath>

From 8b8375a0016104d15a589bb72013656741179b6e Mon Sep 17 00:00:00 2001
From: Peter Collins <pecolli@microsoft.com>
Date: Tue, 25 Jun 2024 16:31:54 -0400
Subject: [PATCH 8/8] [ci] install .NET 8

---
 azure-pipelines.yaml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/azure-pipelines.yaml b/azure-pipelines.yaml
index e8872f9b..f408e360 100644
--- a/azure-pipelines.yaml
+++ b/azure-pipelines.yaml
@@ -48,6 +48,11 @@ jobs:
     inputs:
       version: $(DotNetCoreVersion)
 
+  - task: UseDotNet@2
+    displayName: Use .NET Core 8.0.x
+    inputs:
+      version: 8.0.x
+
   - script: dotnet build Xamarin.Android.Tools.sln -bl:$(Build.ArtifactStagingDirectory)/build.binlog
     displayName: 'Build solution Xamarin.Android.Tools.sln'
 
