diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index f5a4becbd73b40..6b5c05d37231bd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -260,7 +260,21 @@ internal sealed class ContingentProperties internal void SetCompleted() { ManualResetEventSlim? mres = m_completionEvent; - if (mres != null) mres.Set(); + if (mres != null) SetEvent(mres); + } + + internal static void SetEvent(ManualResetEventSlim mres) + { + try + { + mres.Set(); + } + catch (ObjectDisposedException) + { + // The event may have been exposed to external code, in which case it could have been disposed + // prematurely / erroneously. To avoid that causing problems for the unrelated stack trying to + // set the event, eat any disposed exceptions. + } } /// @@ -1483,7 +1497,7 @@ internal ManualResetEventSlim CompletedEvent { // We published the event as unset, but the task has subsequently completed. // Set the event's state properly so that callers don't deadlock. - newMre.Set(); + ContingentProperties.SetEvent(newMre); } } @@ -1619,7 +1633,7 @@ protected virtual void Dispose(bool disposing) // will deadlock; an ensuing Set() will not wake them up. In the event of an AppDomainUnload, // there is no guarantee that anyone else is going to signal the event, and it does no harm to // call Set() twice on m_completionEvent. - if (!ev.IsSet) ev.Set(); + ContingentProperties.SetEvent(ev); // Finally, dispose of the event ev.Dispose(); diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs index a140578160d97f..fa01ab6b75c892 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs @@ -110,6 +110,14 @@ public void WaitOnAsyncWaitHandleTechnique(bool hasReturnType) Assert.False(asyncResult.CompletedSynchronously, "Should not have completed synchronously."); } + [Fact] + public void AsyncWaitHandle_DisposeHandleThenCompleteTask_Succeeds() + { + var tcs = new TaskCompletionSource(); + ((IAsyncResult)tcs.Task).AsyncWaitHandle.Dispose(); + tcs.SetResult(); + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] [InlineData(true)]