Browse Source

Clean up ManualResetEvent usage in tests (#6961)

Chris Ross 7 years ago
parent
commit
7d4b6fccff

+ 10 - 12
src/DefaultBuilder/DefaultBuilder.sln

@@ -77,18 +77,16 @@ Global
 		{766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x64.Build.0 = Release|Any CPU
 		{766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x86.ActiveCfg = Release|Any CPU
 		{766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x86.Build.0 = Release|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x64.Build.0 = Debug|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x86.Build.0 = Debug|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|Any CPU.Build.0 = Release|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x64.ActiveCfg = Release|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x64.Build.0 = Release|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x86.ActiveCfg = Release|Any CPU
-		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x86.Build.0 = Release|Any CPU
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x64.ActiveCfg = Debug|x64
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x64.Build.0 = Debug|x64
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x86.ActiveCfg = Debug|x86
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x86.Build.0 = Debug|x86
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|Any CPU.ActiveCfg = Release|x86
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x64.ActiveCfg = Release|x64
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x64.Build.0 = Release|x64
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x86.ActiveCfg = Release|x86
+		{BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x86.Build.0 = Release|x86
 		{AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|x64.ActiveCfg = Debug|Any CPU

+ 6 - 4
src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs

@@ -7,9 +7,11 @@ using System.Collections.Generic;
 using System.Diagnostics.Tracing;
 using System.Linq;
 using System.Threading;
+using System.Threading.Tasks;
 using Microsoft.AspNetCore.HostFiltering;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
@@ -28,7 +30,7 @@ namespace Microsoft.AspNetCore.Tests
         }
 
         [Fact]
-        public void WebHostConfiguration_HostFilterOptionsAreReloadable()
+        public async Task WebHostConfiguration_HostFilterOptionsAreReloadable()
         {
             var host = WebHost.CreateDefaultBuilder()
                 .Configure(app => { })
@@ -42,15 +44,15 @@ namespace Microsoft.AspNetCore.Tests
 
             Assert.Contains("*", options.AllowedHosts);
 
-            var changed = new ManualResetEvent(false);
+            var changed = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             monitor.OnChange(newOptions =>
             {
-                changed.Set();
+                changed.SetResult(0);
             });
 
             config["AllowedHosts"] = "NewHost";
 
-            Assert.True(changed.WaitOne(TimeSpan.FromSeconds(10)));
+            await changed.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
             options = monitor.CurrentValue;
             Assert.Contains("NewHost", options.AllowedHosts);
         }

+ 34 - 35
src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.TestHost;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Testing;
@@ -460,7 +461,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         [MemberData(nameof(SupportedEncodingsWithBodyLength))]
         public async Task FlushHeaders_SendsHeaders_Compresses(string encoding, int expectedBodyLength)
         {
-            var responseReceived = new ManualResetEvent(false);
+            var responseReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             var builder = new WebHostBuilder()
                 .ConfigureServices(services =>
@@ -470,13 +471,13 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                 .Configure(app =>
                 {
                     app.UseResponseCompression();
-                    app.Run(context =>
+                    app.Run(async context =>
                     {
                         context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
                         context.Response.ContentType = TextPlain;
                         context.Response.Body.Flush();
-                        Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3)));
-                        return context.Response.WriteAsync(new string('a', 100));
+                        await responseReceived.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
+                        await context.Response.WriteAsync(new string('a', 100));
                     });
                 });
 
@@ -487,7 +488,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
             request.Headers.AcceptEncoding.ParseAdd(encoding);
 
             var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
-            responseReceived.Set();
+            responseReceived.SetResult(0);
 
             await response.Content.LoadIntoBufferAsync();
 
@@ -498,7 +499,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         [MemberData(nameof(SupportedEncodingsWithBodyLength))]
         public async Task FlushAsyncHeaders_SendsHeaders_Compresses(string encoding, int expectedBodyLength)
         {
-            var responseReceived = new ManualResetEvent(false);
+            var responseReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             var builder = new WebHostBuilder()
                 .ConfigureServices(services =>
@@ -513,7 +514,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                         context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
                         context.Response.ContentType = TextPlain;
                         await context.Response.Body.FlushAsync();
-                        Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3)));
+                        await responseReceived.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
                         await context.Response.WriteAsync(new string('a', 100));
                     });
                 });
@@ -525,7 +526,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
             request.Headers.AcceptEncoding.ParseAdd(encoding);
 
             var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
-            responseReceived.Set();
+            responseReceived.SetResult(0);
 
             await response.Content.LoadIntoBufferAsync();
 
@@ -536,7 +537,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         [MemberData(nameof(SupportedEncodings))]
         public async Task FlushBody_CompressesAndFlushes(string encoding)
         {
-            var responseReceived = new ManualResetEvent(false);
+            var responseReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             var builder = new WebHostBuilder()
                 .ConfigureServices(services =>
@@ -546,7 +547,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                 .Configure(app =>
                 {
                     app.UseResponseCompression();
-                    app.Run(context =>
+                    app.Run(async context =>
                     {
                         var feature = context.Features.Get<IHttpBodyControlFeature>();
                         if (feature != null)
@@ -558,9 +559,8 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                         context.Response.ContentType = TextPlain;
                         context.Response.Body.Write(new byte[10], 0, 10);
                         context.Response.Body.Flush();
-                        Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3)));
+                        await responseReceived.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
                         context.Response.Body.Write(new byte[90], 0, 90);
-                        return Task.FromResult(0);
                     });
                 });
 
@@ -579,7 +579,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
             var read = await body.ReadAsync(new byte[100], 0, 100);
             Assert.True(read > 0);
 
-            responseReceived.Set();
+            responseReceived.SetResult(0);
 
             read = await body.ReadAsync(new byte[100], 0, 100);
             Assert.True(read > 0);
@@ -589,7 +589,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         [MemberData(nameof(SupportedEncodings))]
         public async Task FlushAsyncBody_CompressesAndFlushes(string encoding)
         {
-            var responseReceived = new ManualResetEvent(false);
+            var responseReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             var builder = new WebHostBuilder()
                 .ConfigureServices(services =>
@@ -605,7 +605,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                         context.Response.ContentType = TextPlain;
                         await context.Response.WriteAsync(new string('a', 10));
                         await context.Response.Body.FlushAsync();
-                        Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3)));
+                        await responseReceived.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
                         await context.Response.WriteAsync(new string('a', 90));
                     });
                 });
@@ -625,7 +625,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
             var read = await body.ReadAsync(new byte[100], 0, 100);
             Assert.True(read > 0);
 
-            responseReceived.Set();
+            responseReceived.SetResult(0);
 
             read = await body.ReadAsync(new byte[100], 0, 100);
             Assert.True(read > 0);
@@ -637,11 +637,11 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         {
             var responseReceived = new[]
             {
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
             };
 
             var builder = new WebHostBuilder()
@@ -652,7 +652,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                 .Configure(app =>
                 {
                     app.UseResponseCompression();
-                    app.Run(context =>
+                    app.Run(async context =>
                     {
                         context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
                         context.Response.ContentType = TextPlain;
@@ -668,9 +668,8 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                         {
                             context.Response.Body.Write(new byte[1], 0, 1);
                             context.Response.Body.Flush();
-                            Assert.True(signal.WaitOne(TimeSpan.FromSeconds(3)));
+                            await signal.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
                         }
-                        return Task.FromResult(0);
                     });
                 });
 
@@ -691,7 +690,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                 var read = await body.ReadAsync(new byte[100], 0, 100);
                 Assert.True(read > 0);
 
-                signal.Set();
+                signal.SetResult(0);
             }
         }
 
@@ -701,11 +700,11 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         {
             var responseReceived = new[]
             {
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
-                new ManualResetEvent(false),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
+                new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously),
             };
 
             var builder = new WebHostBuilder()
@@ -726,7 +725,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                         {
                             await context.Response.WriteAsync("a");
                             await context.Response.Body.FlushAsync();
-                            Assert.True(signal.WaitOne(TimeSpan.FromSeconds(3)));
+                            await signal.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
                         }
                     });
                 });
@@ -748,7 +747,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                 var read = await body.ReadAsync(new byte[100], 0, 100);
                 Assert.True(read > 0);
 
-                signal.Set();
+                signal.SetResult(0);
             }
         }
 
@@ -918,7 +917,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
         [MemberData(nameof(SupportedEncodings))]
         public async Task Dispose_SyncWriteOrFlushNotCalled(string encoding)
         {
-            var responseReceived = new ManualResetEvent(false);
+            var responseReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             var builder = new WebHostBuilder()
                 .ConfigureServices(services =>
@@ -939,7 +938,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
                         context.Response.ContentType = TextPlain;
                         await context.Response.WriteAsync(new string('a', 10));
                         await context.Response.Body.FlushAsync();
-                        Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3)));
+                        await responseReceived.Task.TimeoutAfter(TimeSpan.FromSeconds(3));
                         await context.Response.WriteAsync(new string('a', 90));
                     });
                 });
@@ -959,7 +958,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
             var read = await body.ReadAsync(new byte[100], 0, 100);
             Assert.True(read > 0);
 
-            responseReceived.Set();
+            responseReceived.SetResult(0);
 
             read = await body.ReadAsync(new byte[100], 0, 100);
             Assert.True(read > 0);

+ 15 - 14
src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs

@@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Server.IntegrationTesting;
 using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging.Testing;
@@ -164,25 +165,25 @@ namespace Microsoft.AspNetCore.StaticFiles
         };
 
         [Fact]
-        public void ClientDisconnect_Kestrel_NoWriteExceptionThrown()
+        public Task ClientDisconnect_Kestrel_NoWriteExceptionThrown()
         {
-            ClientDisconnect_NoWriteExceptionThrown(ServerType.Kestrel);
+            return ClientDisconnect_NoWriteExceptionThrown(ServerType.Kestrel);
         }
 
         [ConditionalFact]
         [OSSkipCondition(OperatingSystems.Linux)]
         [OSSkipCondition(OperatingSystems.MacOSX)]
-        public void ClientDisconnect_WebListener_NoWriteExceptionThrown()
+        public Task ClientDisconnect_WebListener_NoWriteExceptionThrown()
         {
-            ClientDisconnect_NoWriteExceptionThrown(ServerType.HttpSys);
+            return ClientDisconnect_NoWriteExceptionThrown(ServerType.HttpSys);
         }
 
-        private void ClientDisconnect_NoWriteExceptionThrown(ServerType serverType)
+        private async Task ClientDisconnect_NoWriteExceptionThrown(ServerType serverType)
         {
             var interval = TimeSpan.FromSeconds(15);
-            var requestReceived = new ManualResetEvent(false);
-            var requestCancelled = new ManualResetEvent(false);
-            var responseComplete = new ManualResetEvent(false);
+            var requestReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var requestCancelled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var responseComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             Exception exception = null;
             var builder = new WebHostBuilder()
                 .ConfigureServices(services => services.AddSingleton(LoggerFactory))
@@ -193,8 +194,8 @@ namespace Microsoft.AspNetCore.StaticFiles
                     {
                         try
                         {
-                            requestReceived.Set();
-                            Assert.True(requestCancelled.WaitOne(interval), "not cancelled");
+                            requestReceived.SetResult(0);
+                            await requestCancelled.Task.TimeoutAfter(interval);
                             Assert.True(context.RequestAborted.WaitHandle.WaitOne(interval), "not aborted");
                             await next();
                         }
@@ -202,7 +203,7 @@ namespace Microsoft.AspNetCore.StaticFiles
                         {
                             exception = ex;
                         }
-                        responseComplete.Set();
+                        responseComplete.SetResult(0);
                     });
                     app.UseStaticFiles();
                 });
@@ -220,13 +221,13 @@ namespace Microsoft.AspNetCore.StaticFiles
             {
                 // We don't use HttpClient here because it's disconnect behavior varies across platforms.
                 var socket = SendSocketRequestAsync(server.GetAddress(), "/TestDocument1MB.txt");
-                Assert.True(requestReceived.WaitOne(interval), "not received");
+                await requestReceived.Task.TimeoutAfter(interval);
 
                 socket.LingerState = new LingerOption(true, 0);
                 socket.Dispose();
-                requestCancelled.Set();
+                requestCancelled.SetResult(0);
 
-                Assert.True(responseComplete.WaitOne(interval), "not completed");
+                await responseComplete.Task.TimeoutAfter(interval);
                 Assert.Null(exception);
             }
         }

+ 23 - 22
src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs

@@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.ViewEngines;
 using Microsoft.AspNetCore.Mvc.ViewFeatures;
 using Microsoft.AspNetCore.Razor.TagHelpers;
 using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.Internal;
 using Microsoft.Extensions.Primitives;
@@ -545,9 +546,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
             // Arrange
             var id = "unique-id";
             var childContent = "some-content";
-            var resetEvent1 = new ManualResetEvent(false);
-            var resetEvent2 = new ManualResetEvent(false);
-            var resetEvent3 = new ManualResetEvent(false);
+            var event1 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event2 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event3 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var calls = 0;
             var cache = new MemoryCache(new MemoryCacheOptions());
 
@@ -560,7 +561,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
                 getChildContentAsync: (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent2.Set();
+                    event2.SetResult(0);
 
                     var tagHelperContent = new DefaultTagHelperContent();
                     tagHelperContent.SetHtmlContent(childContent);
@@ -570,14 +571,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
             var tagHelperOutput2 = new TagHelperOutput(
                 "cache",
                 new TagHelperAttributeList(),
-                getChildContentAsync: (useCachedResult, encoder) =>
+                getChildContentAsync: async (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent3.WaitOne(5000);
+                    await event3.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
 
                     var tagHelperContent = new DefaultTagHelperContent();
                     tagHelperContent.SetHtmlContent(childContent);
-                    return Task.FromResult<TagHelperContent>(tagHelperContent);
+                    return tagHelperContent;
                 });
 
             var cacheTagHelper1 = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(cache), new HtmlTestEncoder())
@@ -596,18 +597,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
 
             var task1 = Task.Run(async () =>
             {
-                resetEvent1.WaitOne(5000);
+                await event1.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1);
-                resetEvent3.Set();
+                event3.SetResult(0);
             });
 
             var task2 = Task.Run(async () =>
             {
-                resetEvent2.WaitOne(5000);
+                await event2.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await cacheTagHelper2.ProcessAsync(tagHelperContext1, tagHelperOutput2);
             });
 
-            resetEvent1.Set();
+            event1.SetResult(0);
             await Task.WhenAll(task1, task2);
 
             // Assert
@@ -630,9 +631,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
             // Arrange
             var id = "unique-id";
             var childContent = "some-content";
-            var resetEvent1 = new ManualResetEvent(false);
-            var resetEvent2 = new ManualResetEvent(false);
-            var resetEvent3 = new ManualResetEvent(false);
+            var event1 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event2 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event3 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var calls = 0;
             var cache = new MemoryCache(new MemoryCacheOptions());
 
@@ -645,7 +646,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
                 getChildContentAsync: (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent2.Set();
+                    event2.SetResult(0);
 
                     throw new Exception();
                 });
@@ -653,14 +654,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
             var tagHelperOutput2 = new TagHelperOutput(
                 "cache",
                 new TagHelperAttributeList(),
-                getChildContentAsync: (useCachedResult, encoder) =>
+                getChildContentAsync: async (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent3.WaitOne(5000);
+                    await event3.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
 
                     var tagHelperContent = new DefaultTagHelperContent();
                     tagHelperContent.SetHtmlContent(childContent);
-                    return Task.FromResult<TagHelperContent>(tagHelperContent);
+                    return tagHelperContent;
                 });
 
             var cacheTagHelper1 = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(cache), new HtmlTestEncoder())
@@ -679,18 +680,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
 
             var task1 = Task.Run(async () =>
             {
-                resetEvent1.WaitOne(5000);
+                await event1.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await Assert.ThrowsAsync<Exception>(() => cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1));
-                resetEvent3.Set();
+                event3.SetResult(0);
             });
 
             var task2 = Task.Run(async () =>
             {
-                resetEvent2.WaitOne(5000);
+                await event2.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2);
             });
 
-            resetEvent1.Set();
+            event1.SetResult(0);
             await Task.WhenAll(task1, task2);
 
             // Assert

+ 23 - 22
src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DistributedCacheTagHelperTest.cs

@@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.ViewEngines;
 using Microsoft.AspNetCore.Mvc.ViewFeatures;
 using Microsoft.AspNetCore.Razor.TagHelpers;
 using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.Caching.Distributed;
 using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.Internal;
@@ -538,9 +539,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
         {
             // Arrange
             var childContent = "some-content";
-            var resetEvent1 = new ManualResetEvent(false);
-            var resetEvent2 = new ManualResetEvent(false);
-            var resetEvent3 = new ManualResetEvent(false);
+            var event1 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event2 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event3 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var calls = 0;
             var formatter = GetFormatter();
             var storage = GetStorage();
@@ -559,7 +560,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
                 getChildContentAsync: (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent2.Set();
+                    event2.SetResult(0);
 
                     var tagHelperContent = new DefaultTagHelperContent();
                     tagHelperContent.SetHtmlContent(childContent);
@@ -569,14 +570,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
             var tagHelperOutput2 = new TagHelperOutput(
                 "distributed-cache",
                 new TagHelperAttributeList(),
-                getChildContentAsync: (useCachedResult, encoder) =>
+                getChildContentAsync: async (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent3.WaitOne(5000);
+                    await event3.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
 
                     var tagHelperContent = new DefaultTagHelperContent();
                     tagHelperContent.SetHtmlContent(childContent);
-                    return Task.FromResult<TagHelperContent>(tagHelperContent);
+                    return tagHelperContent;
                 });
 
             var cacheTagHelper1 = new DistributedCacheTagHelper(
@@ -599,18 +600,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
 
             var task1 = Task.Run(async () =>
             {
-                resetEvent1.WaitOne(5000);
+                await event1.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1);
-                resetEvent3.Set();
+                event3.SetResult(0);
             });
 
             var task2 = Task.Run(async () =>
             {
-                resetEvent2.WaitOne(5000);
+                await event2.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await cacheTagHelper2.ProcessAsync(tagHelperContext1, tagHelperOutput2);
             });
 
-            resetEvent1.Set();
+            event1.SetResult(0);
             await Task.WhenAll(task1, task2);
 
             // Assert
@@ -632,9 +633,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
         {
             // Arrange
             var childContent = "some-content";
-            var resetEvent1 = new ManualResetEvent(false);
-            var resetEvent2 = new ManualResetEvent(false);
-            var resetEvent3 = new ManualResetEvent(false);
+            var event1 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event2 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var event3 = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var calls = 0;
             var formatter = GetFormatter();
             var storage = GetStorage();
@@ -653,7 +654,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
                 getChildContentAsync: (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent2.Set();
+                    event2.SetResult(0);
 
                     throw new Exception();
                 });
@@ -661,14 +662,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
             var tagHelperOutput2 = new TagHelperOutput(
                 "distributed-cache",
                 new TagHelperAttributeList(),
-                getChildContentAsync: (useCachedResult, encoder) =>
+                getChildContentAsync: async (useCachedResult, encoder) =>
                 {
                     calls++;
-                    resetEvent3.WaitOne(5000);
+                    await event3.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
 
                     var tagHelperContent = new DefaultTagHelperContent();
                     tagHelperContent.SetHtmlContent(childContent);
-                    return Task.FromResult<TagHelperContent>(tagHelperContent);
+                    return tagHelperContent;
                 });
 
             var cacheTagHelper1 = new DistributedCacheTagHelper(
@@ -691,18 +692,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
 
             var task1 = Task.Run(async () =>
             {
-                resetEvent1.WaitOne(5000);
+                await event1.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await Assert.ThrowsAsync<Exception>(() => cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1));
-                resetEvent3.Set();
+                event3.SetResult(0);
             });
 
             var task2 = Task.Run(async () =>
             {
-                resetEvent2.WaitOne(5000);
+                await event2.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2);
             });
 
-            resetEvent1.Set();
+            event1.SetResult(0);
             await Task.WhenAll(task1, task2);
 
             // Assert

+ 4 - 3
src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs

@@ -8,6 +8,7 @@ using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
@@ -167,7 +168,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [ConditionalFact]
         public async Task AuthTypes_AccessUserInOnCompleted_Success()
         {
-            var completed = new ManualResetEvent(false);
+            var completed = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             string userName = null;
             var authTypes = AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM;
             using (var server = Utilities.CreateDynamicHost(authTypes, DenyAnoymous, out var address, httpContext =>
@@ -178,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                 httpContext.Response.OnCompleted(() =>
                 {
                     userName = httpContext.User.Identity.Name;
-                    completed.Set();
+                    completed.SetResult(0);
                     return Task.FromResult(0);
                 });
                 return Task.FromResult(0);
@@ -186,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             {
                 var response = await SendRequestAsync(address, useDefaultCredentials: true);
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-                Assert.True(completed.WaitOne(TimeSpan.FromSeconds(5)));
+                await completed.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
                 Assert.False(string.IsNullOrEmpty(userName));
             }
         }

+ 10 - 9
src/Servers/HttpSys/test/FunctionalTests/Listener/ServerTests.cs

@@ -9,6 +9,7 @@ using System.Net.Sockets;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
@@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
         public async Task Server_TokenRegisteredAfterClientDisconnects_CallCanceled()
         {
             var interval = TimeSpan.FromSeconds(1);
-            var canceled = new ManualResetEvent(false);
+            var canceled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             string address;
             using (var server = Utilities.CreateHttpServer(out address))
@@ -36,11 +37,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
 
                     var ct = context.DisconnectToken;
                     Assert.True(ct.CanBeCanceled, "CanBeCanceled");
-                    ct.Register(() => canceled.Set());
+                    ct.Register(() => canceled.SetResult(0));
                     Assert.True(ct.WaitHandle.WaitOne(interval));
                     Assert.True(ct.IsCancellationRequested, "IsCancellationRequested");
 
-                    Assert.True(canceled.WaitOne(interval), "canceled");
+                    await canceled.Task.TimeoutAfter(interval);
 
                     context.Dispose();
                 }
@@ -51,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
         public async Task Server_TokenRegisteredAfterResponseSent_Success()
         {
             var interval = TimeSpan.FromSeconds(1);
-            var canceled = new ManualResetEvent(false);
+            var canceled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             string address;
             using (var server = Utilities.CreateHttpServer(out address))
@@ -69,11 +70,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
 
                     var ct = context.DisconnectToken;
                     Assert.False(ct.CanBeCanceled, "CanBeCanceled");
-                    ct.Register(() => canceled.Set());
+                    ct.Register(() => canceled.SetResult(0));
                     Assert.False(ct.WaitHandle.WaitOne(interval));
                     Assert.False(ct.IsCancellationRequested, "IsCancellationRequested");
 
-                    Assert.False(canceled.WaitOne(interval), "canceled");
+                    Assert.False(canceled.Task.IsCompleted, "canceled");
                 }
             }
         }
@@ -82,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
         public async Task Server_ConnectionCloseHeader_CancellationTokenFires()
         {
             var interval = TimeSpan.FromSeconds(1);
-            var canceled = new ManualResetEvent(false);
+            var canceled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
             string address;
             using (var server = Utilities.CreateHttpServer(out address))
@@ -93,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
                 var ct = context.DisconnectToken;
                 Assert.True(ct.CanBeCanceled, "CanBeCanceled");
                 Assert.False(ct.IsCancellationRequested, "IsCancellationRequested");
-                ct.Register(() => canceled.Set());
+                ct.Register(() => canceled.SetResult(0));
 
                 context.Response.Headers["Connection"] = "close";
 
@@ -102,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
                 await writer.WriteAsync("Hello World");
                 await writer.FlushAsync();
 
-                Assert.True(canceled.WaitOne(interval), "Disconnected");
+                await canceled.Task.TimeoutAfter(interval);
                 Assert.True(ct.IsCancellationRequested, "IsCancellationRequested");
 
                 var response = await responseTask;

+ 14 - 28
src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs

@@ -10,6 +10,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
@@ -104,25 +105,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
         public async Task OpaqueUpgrade_GetUpgrade_Success()
         {
-            ManualResetEvent waitHandle = new ManualResetEvent(false);
-            bool? upgraded = null;
-            string address;
-            using (Utilities.CreateHttpServer(out address, async httpContext =>
+            var upgraded = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, async httpContext =>
             {
                 httpContext.Response.Headers["Upgrade"] = "websocket"; // Win8.1 blocks anything but WebSockets
                 var opaqueFeature = httpContext.Features.Get<IHttpUpgradeFeature>();
                 Assert.NotNull(opaqueFeature);
                 Assert.True(opaqueFeature.IsUpgradableRequest);
                 await opaqueFeature.UpgradeAsync();
-                upgraded = true;
-                waitHandle.Set();
+                upgraded.SetResult(true);
             }))
             {
                 using (Stream stream = await SendOpaqueRequestAsync("GET", address))
                 {
-                    Assert.True(waitHandle.WaitOne(TimeSpan.FromSeconds(1)), "Timed out");
-                    Assert.True(upgraded.HasValue, "Upgraded not set");
-                    Assert.True(upgraded.Value, "Upgrade failed");
+                    Assert.True(await upgraded.Task.TimeoutAfter(TimeSpan.FromSeconds(1)));
                 }
             }
         }
@@ -131,10 +127,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
         public async Task OpaqueUpgrade_GetUpgrade_NotAffectedByMaxRequestBodyLimit()
         {
-            ManualResetEvent waitHandle = new ManualResetEvent(false);
-            bool? upgraded = null;
-            string address;
-            using (Utilities.CreateHttpServer(out address, async httpContext =>
+            var upgraded = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, async httpContext =>
             {
                 var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
                 Assert.NotNull(feature);
@@ -150,16 +144,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                 Assert.Null(feature.MaxRequestBodySize);
                 Assert.Throws<InvalidOperationException>(() => feature.MaxRequestBodySize = 12);
                 Assert.Equal(15, await stream.ReadAsync(new byte[15], 0, 15));
-                upgraded = true;
-                waitHandle.Set();
+                upgraded.SetResult(true);
             }, options => options.MaxRequestBodySize = 10))
             {
                 using (Stream stream = await SendOpaqueRequestAsync("GET", address))
                 {
                     stream.Write(new byte[15], 0, 15);
-                    Assert.True(waitHandle.WaitOne(TimeSpan.FromSeconds(10)), "Timed out");
-                    Assert.True(upgraded.HasValue, "Upgraded not set");
-                    Assert.True(upgraded.Value, "Upgrade failed");
+                    Assert.True(await upgraded.Task.TimeoutAfter(TimeSpan.FromSeconds(10)));
                 }
             }
         }
@@ -169,10 +160,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task OpaqueUpgrade_WithOnStarting_CallbackCalled()
         {
             var callbackCalled = false;
-            var waitHandle = new ManualResetEvent(false);
-            bool? upgraded = null;
-            string address;
-            using (Utilities.CreateHttpServer(out address, async httpContext =>
+            var upgraded = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, async httpContext =>
             {
                 httpContext.Response.OnStarting(_ =>
                 {
@@ -184,15 +173,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                 Assert.NotNull(opaqueFeature);
                 Assert.True(opaqueFeature.IsUpgradableRequest);
                 await opaqueFeature.UpgradeAsync();
-                upgraded = true;
-                waitHandle.Set();
+                upgraded.SetResult(true);
             }))
             {
                 using (Stream stream = await SendOpaqueRequestAsync("GET", address))
                 {
-                    Assert.True(waitHandle.WaitOne(TimeSpan.FromSeconds(1)), "Timed out");
-                    Assert.True(upgraded.HasValue, "Upgraded not set");
-                    Assert.True(upgraded.Value, "Upgrade failed");
+                    Assert.True(await upgraded.Task.TimeoutAfter(TimeSpan.FromSeconds(1)));
                     Assert.True(callbackCalled, "Callback not called");
                 }
             }
@@ -364,4 +350,4 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             }
         }
     }
-}
+}

+ 6 - 10
src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs

@@ -10,6 +10,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
@@ -157,10 +158,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [ConditionalFact]
         public async Task ResponseBody_WriteContentLengthExtraWritten_Throws()
         {
-            var waitHandle = new ManualResetEvent(false);
-            bool? appThrew = null;
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            var requestThrew = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, httpContext =>
             {
                 try
                 {
@@ -168,13 +167,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                     httpContext.Response.Headers["Content-lenGth"] = " 10 ";
                     httpContext.Response.Body.Write(new byte[10], 0, 10);
                     httpContext.Response.Body.Write(new byte[9], 0, 9);
-                    appThrew = false;
+                    requestThrew.SetResult(false);
                 }
                 catch (Exception)
                 {
-                    appThrew = true;
+                    requestThrew.SetResult(true);
                 }
-                waitHandle.Set();
                 return Task.FromResult(0);
             }))
             {
@@ -188,9 +186,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                 Assert.Null(response.Headers.TransferEncodingChunked);
                 Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
 
-                Assert.True(waitHandle.WaitOne(100));
-                Assert.True(appThrew.HasValue, "appThrew.HasValue");
-                Assert.True(appThrew.Value, "appThrew.Value");
+                Assert.True(await requestThrew.Task.TimeoutAfter(TimeSpan.FromSeconds(10)));
             }
         }
 

+ 6 - 13
src/Servers/HttpSys/test/FunctionalTests/ResponseSendFileTests.cs

@@ -12,6 +12,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
@@ -74,34 +75,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [ConditionalFact]
         public async Task ResponseSendFile_MissingFile_Throws()
         {
-            var waitHandle = new ManualResetEvent(false);
-            bool? appThrew = null;
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            var appThrew = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, httpContext =>
             {
                 var sendFile = httpContext.Features.Get<IHttpSendFileFeature>();
                 try
                 {
                     sendFile.SendFileAsync(string.Empty, 0, null, CancellationToken.None).Wait();
-                    appThrew = false;
+                    appThrew.SetResult(false);
                 }
                 catch (Exception)
                 {
-                    appThrew = true;
+                    appThrew.SetResult(true);
                     throw;
                 }
-                finally
-                {
-                    waitHandle.Set();
-                }
                 return Task.FromResult(0);
             }))
             {
                 HttpResponseMessage response = await SendRequestAsync(address);
                 Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
-                Assert.True(waitHandle.WaitOne(100));
-                Assert.True(appThrew.HasValue, "appThrew.HasValue");
-                Assert.True(appThrew.Value, "appThrew.Value");
+                Assert.True(await appThrew.Task.TimeoutAfter(TimeSpan.FromSeconds(10)));
             }
         }
         

+ 23 - 24
src/Servers/HttpSys/test/FunctionalTests/ResponseTests.cs

@@ -8,6 +8,7 @@ using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
@@ -123,21 +124,21 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [ConditionalFact]
         public async Task Response_Empty_CallsOnStartingAndOnCompleted()
         {
-            var onStartingCalled = new ManualResetEvent(false);
-            var onCompletedCalled = new ManualResetEvent(false);
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            var onStartingCalled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var onCompletedCalled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+            using (Utilities.CreateHttpServer(out var address, httpContext =>
             {
                 httpContext.Response.OnStarting(state =>
                 {
                     Assert.Same(state, httpContext);
-                    onStartingCalled.Set();
+                    onStartingCalled.SetResult(0);
                     return Task.FromResult(0);
                 }, httpContext);
                 httpContext.Response.OnCompleted(state =>
                 {
                     Assert.Same(state, httpContext);
-                    onCompletedCalled.Set();
+                    onCompletedCalled.SetResult(0);
                     return Task.FromResult(0);
                 }, httpContext);
                 return Task.FromResult(0);
@@ -145,29 +146,28 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             {
                 var response = await SendRequestAsync(address);
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-                Assert.True(onStartingCalled.WaitOne(0));
+                await onStartingCalled.Task.TimeoutAfter(TimeSpan.FromSeconds(1));
                 // Fires after the response completes
-                Assert.True(onCompletedCalled.WaitOne(TimeSpan.FromSeconds(5)));
+                await onCompletedCalled.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
             }
         }
 
         [ConditionalFact]
         public async Task Response_OnStartingThrows_StillCallsOnCompleted()
         {
-            var onStartingCalled = new ManualResetEvent(false);
-            var onCompletedCalled = new ManualResetEvent(false);
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            var onStartingCalled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var onCompletedCalled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, httpContext =>
             {
                 httpContext.Response.OnStarting(state =>
                 {
-                    onStartingCalled.Set();
+                    onStartingCalled.SetResult(0);
                     throw new Exception("Failed OnStarting");
                 }, httpContext);
                 httpContext.Response.OnCompleted(state =>
                 {
                     Assert.Same(state, httpContext);
-                    onCompletedCalled.Set();
+                    onCompletedCalled.SetResult(0);
                     return Task.FromResult(0);
                 }, httpContext);
                 return Task.FromResult(0);
@@ -175,29 +175,28 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             {
                 var response = await SendRequestAsync(address);
                 Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
-                Assert.True(onStartingCalled.WaitOne(0));
+                await onStartingCalled.Task.TimeoutAfter(TimeSpan.FromSeconds(1));
                 // Fires after the response completes
-                Assert.True(onCompletedCalled.WaitOne(TimeSpan.FromSeconds(5)));
+                await onCompletedCalled.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
             }
         }
 
         [ConditionalFact]
         public async Task Response_OnStartingThrowsAfterWrite_WriteThrowsAndStillCallsOnCompleted()
         {
-            var onStartingCalled = new ManualResetEvent(false);
-            var onCompletedCalled = new ManualResetEvent(false);
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            var onStartingCalled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var onCompletedCalled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (Utilities.CreateHttpServer(out var address, httpContext =>
             {
                 httpContext.Response.OnStarting(state =>
                 {
-                    onStartingCalled.Set();
+                    onStartingCalled.SetResult(0);
                     throw new InvalidTimeZoneException("Failed OnStarting");
                 }, httpContext);
                 httpContext.Response.OnCompleted(state =>
                 {
                     Assert.Same(state, httpContext);
-                    onCompletedCalled.Set();
+                    onCompletedCalled.SetResult(0);
                     return Task.FromResult(0);
                 }, httpContext);
                 Assert.Throws<InvalidTimeZoneException>(() => httpContext.Response.Body.Write(new byte[10], 0, 10));
@@ -206,9 +205,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             {
                 var response = await SendRequestAsync(address);
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-                Assert.True(onStartingCalled.WaitOne(0));
+                await onStartingCalled.Task.TimeoutAfter(TimeSpan.FromSeconds(1));
                 // Fires after the response completes
-                Assert.True(onCompletedCalled.WaitOne(TimeSpan.FromSeconds(5)));
+                await onCompletedCalled.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
             }
         }
 

+ 81 - 95
src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs

@@ -69,17 +69,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_ShutdownDuringRequest_Success()
         {
             Task<string> responseTask;
-            ManualResetEvent received = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, httpContext =>
                 {
-                    received.Set();
+                    received.SetResult(0);
                     httpContext.Response.ContentLength = 11;
                     return httpContext.Response.WriteAsync("Hello World");
                 }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(10000));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                 await server.StopAsync(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
             }
             string response = await responseTask;
@@ -90,21 +89,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_DisposeWithoutStopDuringRequest_Aborts()
         {
             Task<string> responseTask;
-            var received = new ManualResetEvent(false);
-            var stopped = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var stopped = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
             {
-                received.Set();
-                Assert.True(stopped.WaitOne(TimeSpan.FromSeconds(10)));
+                received.SetResult(0);
+                await stopped.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                 httpContext.Response.ContentLength = 11;
-                return httpContext.Response.WriteAsync("Hello World");
+                await httpContext.Response.WriteAsync("Hello World");
             }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(TimeSpan.FromSeconds(10)));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
             }
-            stopped.Set();
+            stopped.SetResult(0);
             await Assert.ThrowsAsync<HttpRequestException>(async () => await responseTask);
         }
 
@@ -112,24 +110,21 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_ShutdownDuringLongRunningRequest_TimesOut()
         {
             Task<string> responseTask;
-            var received = new ManualResetEvent(false);
-            bool? shutdown = null;
-            var waitForShutdown = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var shutdown = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
             {
-                received.Set();
-                shutdown = waitForShutdown.WaitOne(TimeSpan.FromSeconds(15));
+                received.SetResult(0);
+                await shutdown.Task.TimeoutAfter(TimeSpan.FromSeconds(15));
                 httpContext.Response.ContentLength = 11;
-                return httpContext.Response.WriteAsync("Hello World");
+                await httpContext.Response.WriteAsync("Hello World");
             }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(TimeSpan.FromSeconds(10)));
-                Assert.False(shutdown.HasValue);
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                 await server.StopAsync(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
             }
-            waitForShutdown.Set();
+            shutdown.SetResult(0);
             await Assert.ThrowsAsync<HttpRequestException>(async () => await responseTask);
         }
 
@@ -217,64 +212,59 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         [ConditionalFact]
         public async Task Server_ClientDisconnects_CallCanceled()
         {
-            TimeSpan interval = TimeSpan.FromSeconds(10);
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent aborted = new ManualResetEvent(false);
-            ManualResetEvent canceled = new ManualResetEvent(false);
+            var interval = TimeSpan.FromSeconds(10);
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var aborted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var canceled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            using (Utilities.CreateHttpServer(out var address, async httpContext =>
             {
-                CancellationToken ct = httpContext.RequestAborted;
+                var ct = httpContext.RequestAborted;
                 Assert.True(ct.CanBeCanceled, "CanBeCanceled");
                 Assert.False(ct.IsCancellationRequested, "IsCancellationRequested");
-                ct.Register(() => canceled.Set());
-                received.Set();
-                Assert.True(aborted.WaitOne(interval), "Aborted");
+                ct.Register(() => canceled.SetResult(0));
+                received.SetResult(0);
+                await aborted.Task.TimeoutAfter(interval);
                 Assert.True(ct.WaitHandle.WaitOne(interval), "CT Wait");
                 Assert.True(ct.IsCancellationRequested, "IsCancellationRequested");
-                return Task.FromResult(0);
             }))
             {
                 // Note: System.Net.Sockets does not RST the connection by default, it just FINs.
                 // Http.Sys's disconnect notice requires a RST.
                 using (var client = await SendHungRequestAsync("GET", address))
                 {
-                    Assert.True(received.WaitOne(interval), "Receive Timeout");
+                    await received.Task.TimeoutAfter(interval);
 
                     // Force a RST
                     client.LingerState = new LingerOption(true, 0);
                 }
-                aborted.Set();
-                Assert.True(canceled.WaitOne(interval), "canceled");
+                aborted.SetResult(0);
+                await canceled.Task.TimeoutAfter(interval);
             }
         }
 
         [ConditionalFact]
         public async Task Server_Abort_CallCanceled()
         {
-            TimeSpan interval = TimeSpan.FromSeconds(100);
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent aborted = new ManualResetEvent(false);
-            ManualResetEvent canceled = new ManualResetEvent(false);
+            var interval = TimeSpan.FromSeconds(10);
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var canceled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
 
-            string address;
-            using (Utilities.CreateHttpServer(out address, httpContext =>
+            using (Utilities.CreateHttpServer(out var address, async httpContext =>
             {
                 CancellationToken ct = httpContext.RequestAborted;
                 Assert.True(ct.CanBeCanceled, "CanBeCanceled");
                 Assert.False(ct.IsCancellationRequested, "IsCancellationRequested");
-                ct.Register(() => canceled.Set());
-                received.Set();
+                ct.Register(() => canceled.SetResult(0));
+                received.SetResult(0);
                 httpContext.Abort();
-                Assert.True(canceled.WaitOne(interval), "Aborted");
+                await canceled.Task.TimeoutAfter(interval);
                 Assert.True(ct.IsCancellationRequested, "IsCancellationRequested");
-                return Task.FromResult(0);
             }))
             {
                 using (var client = await SendHungRequestAsync("GET", address))
                 {
-                    Assert.True(received.WaitOne(interval), "Receive Timeout");
+                    await received.Task.TimeoutAfter(interval);
                     Assert.Throws<IOException>(() => client.GetStream().Read(new byte[10], 0, 10));
                 }
             }
@@ -423,19 +413,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_MultipleStopAsyncCallsWaitForRequestsToDrain_Success()
         {
             Task<string> responseTask;
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent run = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var run = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
                 {
-                    received.Set();
-                    Assert.True(run.WaitOne(TimeSpan.FromSeconds(10)));
+                    received.SetResult(0);
+                    await run.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                     httpContext.Response.ContentLength = 11;
-                    return httpContext.Response.WriteAsync("Hello World");
+                    await httpContext.Response.WriteAsync("Hello World");
                 }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(TimeSpan.FromSeconds(10)));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
 
                 var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
                 var stopTask1 = server.StopAsync(cts.Token);
@@ -446,11 +435,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                 Assert.False(stopTask2.IsCompleted);
                 Assert.False(stopTask3.IsCompleted);
 
-                run.Set();
+                run.SetResult(0);
 
                 await Task.WhenAll(stopTask1, stopTask2, stopTask3).TimeoutAfter(TimeSpan.FromSeconds(10));
             }
-            string response = await responseTask;
+            var response = await responseTask;
             Assert.Equal("Hello World", response);
         }
 
@@ -458,19 +447,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_MultipleStopAsyncCallsCompleteOnCancellation_SameToken_Success()
         {
             Task<string> responseTask;
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent run = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var run = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
                 {
-                    received.Set();
-                    Assert.True(run.WaitOne(TimeSpan.FromSeconds(10)));
+                    received.SetResult(0);
+                    await run.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                     httpContext.Response.ContentLength = 11;
-                    return httpContext.Response.WriteAsync("Hello World");
+                    await httpContext.Response.WriteAsync("Hello World");
                 }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(TimeSpan.FromSeconds(10)));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
 
                 var cts = new CancellationTokenSource();
                 var stopTask1 = server.StopAsync(cts.Token);
@@ -485,7 +473,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
 
                 await Task.WhenAll(stopTask1, stopTask2, stopTask3).TimeoutAfter(TimeSpan.FromSeconds(10));
 
-                run.Set();
+                run.SetResult(0);
 
                 string response = await responseTask;
                 Assert.Equal("Hello World", response);
@@ -496,19 +484,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_MultipleStopAsyncCallsCompleteOnSingleCancellation_FirstToken_Success()
         {
             Task<string> responseTask;
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent run = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var run = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
                 {
-                    received.Set();
-                    Assert.True(run.WaitOne(TimeSpan.FromSeconds(10)));
+                    received.SetResult(0);
+                    await run.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                     httpContext.Response.ContentLength = 11;
-                    return httpContext.Response.WriteAsync("Hello World");
+                    await httpContext.Response.WriteAsync("Hello World");
                 }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(TimeSpan.FromSeconds(10)));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
 
                 var cts = new CancellationTokenSource();
                 var stopTask1 = server.StopAsync(cts.Token);
@@ -523,7 +510,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
 
                 await Task.WhenAll(stopTask1, stopTask2, stopTask3).TimeoutAfter(TimeSpan.FromSeconds(10));
 
-                run.Set();
+                run.SetResult(0);
 
                 string response = await responseTask;
                 Assert.Equal("Hello World", response);
@@ -534,19 +521,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_MultipleStopAsyncCallsCompleteOnSingleCancellation_SubsequentToken_Success()
         {
             Task<string> responseTask;
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent run = new ManualResetEvent(false);
-            string address;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var run = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
                 {
-                    received.Set();
-                    Assert.True(run.WaitOne(TimeSpan.FromSeconds(10)));
+                    received.SetResult(0);
+                    await run.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
                     httpContext.Response.ContentLength = 11;
-                    return httpContext.Response.WriteAsync("Hello World");
+                    await httpContext.Response.WriteAsync("Hello World");
                 }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(10000));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
 
                 var cts = new CancellationTokenSource();
                 var stopTask1 = server.StopAsync(new CancellationTokenSource().Token);
@@ -561,7 +547,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
 
                 await Task.WhenAll(stopTask1, stopTask2, stopTask3).TimeoutAfter(TimeSpan.FromSeconds(10));
 
-                run.Set();
+                run.SetResult(0);
 
                 string response = await responseTask;
                 Assert.Equal("Hello World", response);
@@ -572,21 +558,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         public async Task Server_DisposeContinuesPendingStopAsyncCalls()
         {
             Task<string> responseTask;
-            ManualResetEvent received = new ManualResetEvent(false);
-            ManualResetEvent run = new ManualResetEvent(false);
-            string address;
+            var received = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var run = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             Task stopTask1;
             Task stopTask2;
-            using (var server = Utilities.CreateHttpServer(out address, httpContext =>
+            using (var server = Utilities.CreateHttpServer(out var address, async httpContext =>
                 {
-                    received.Set();
-                    Assert.True(run.WaitOne(TimeSpan.FromSeconds(10)));
+                    received.SetResult(0);
+                    await run.Task.TimeoutAfter(TimeSpan.FromSeconds(15));
                     httpContext.Response.ContentLength = 11;
-                    return httpContext.Response.WriteAsync("Hello World");
+                    await httpContext.Response.WriteAsync("Hello World");
                 }))
             {
                 responseTask = SendRequestAsync(address);
-                Assert.True(received.WaitOne(TimeSpan.FromSeconds(10)));
+                await received.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
 
                 stopTask1 = server.StopAsync(new CancellationTokenSource().Token);
                 stopTask2 = server.StopAsync(new CancellationTokenSource().Token);
@@ -596,6 +581,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             }
 
             await Task.WhenAll(stopTask1, stopTask2).TimeoutAfter(TimeSpan.FromSeconds(10));
+            run.SetResult(0);
         }
 
         [ConditionalFact]

+ 3 - 31
src/Servers/HttpSys/test/FunctionalTests/Utilities.cs

@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Hosting.Server;
 using Microsoft.AspNetCore.Hosting.Server.Features;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
@@ -160,37 +161,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             return server;
         }
 
-        internal static Task WithTimeout(this Task task) => task.WithTimeout(DefaultTimeout);
+        internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout);
 
-        internal static async Task WithTimeout(this Task task, TimeSpan timeout)
-        {
-            var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
-
-            if (completedTask == task)
-            {
-                await task;
-                return;
-            }
-            else
-            {
-                throw new TimeoutException("The task has timed out.");
-            }
-        }
-
-        internal static Task<T> WithTimeout<T>(this Task<T> task) => task.WithTimeout(DefaultTimeout);
-
-        internal static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
-        {
-            var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
-
-            if (completedTask == task)
-            {
-                return await task;
-            }
-            else
-            {
-                throw new TimeoutException("The task has timed out.");
-            }
-        }
+        internal static Task<T> WithTimeout<T>(this Task<T> task) => task.TimeoutAfter(DefaultTimeout);
     }
 }

+ 25 - 24
src/Servers/IIS/IISIntegration/test/Tests/IISMiddlewareTests.cs

@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http.Features.Authentication;
 using Microsoft.AspNetCore.TestHost;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.DependencyInjection;
 using Xunit;
 
@@ -81,8 +82,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
         [InlineData("/pathBase", "/pathBase/iisintegration", "Shutdown")]
         public async Task MiddlewareShutsdownGivenANCMShutdown(string pathBase, string requestPath, string shutdownEvent)
         {
-            var requestExecuted = new ManualResetEvent(false);
-            var applicationStoppingFired = new ManualResetEvent(false);
+            var requestExecuted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var applicationStoppingFired = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var builder = new WebHostBuilder()
                 .UseSetting("TOKEN", "TestToken")
                 .UseSetting("PORT", "12345")
@@ -91,11 +92,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
                 .Configure(app =>
                 {
                     var appLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
-                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.Set());
+                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.SetResult(0));
 
                     app.Run(context =>
                     {
-                        requestExecuted.Set();
+                        requestExecuted.SetResult(0);
                         return Task.FromResult(0);
                     });
                 });
@@ -106,8 +107,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
             request.Headers.TryAddWithoutValidation("MS-ASPNETCORE-EVENT", shutdownEvent);
             var response = await server.CreateClient().SendAsync(request);
 
-            Assert.True(applicationStoppingFired.WaitOne(TimeSpan.FromSeconds(5)));
-            Assert.False(requestExecuted.WaitOne(0));
+            await applicationStoppingFired.Task.TimeoutAfter(TimeSpan.FromSeconds(5));
+            Assert.False(requestExecuted.Task.IsCompleted);
             Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
         }
 
@@ -131,8 +132,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
         [MemberData(nameof(InvalidShutdownMethods))]
         public async Task MiddlewareIgnoresShutdownGivenWrongMethod(HttpMethod method)
         {
-            var requestExecuted = new ManualResetEvent(false);
-            var applicationStoppingFired = new ManualResetEvent(false);
+            var requestExecuted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var applicationStoppingFired = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var builder = new WebHostBuilder()
                 .UseSetting("TOKEN", "TestToken")
                 .UseSetting("PORT", "12345")
@@ -141,11 +142,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
                 .Configure(app =>
                 {
                     var appLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
-                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.Set());
+                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.SetResult(0));
 
                     app.Run(context =>
                     {
-                        requestExecuted.Set();
+                        requestExecuted.SetResult(0);
                         return Task.FromResult(0);
                     });
                 });
@@ -156,8 +157,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
             request.Headers.TryAddWithoutValidation("MS-ASPNETCORE-EVENT", "shutdown");
             var response = await server.CreateClient().SendAsync(request);
 
-            Assert.False(applicationStoppingFired.WaitOne(TimeSpan.FromSeconds(1)));
-            Assert.True(requestExecuted.WaitOne(0));
+            Assert.False(applicationStoppingFired.Task.IsCompleted);
+            await requestExecuted.Task.TimeoutAfter(TimeSpan.FromSeconds(1));
             Assert.Equal(HttpStatusCode.OK, response.StatusCode);
         }
 
@@ -167,8 +168,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
         [InlineData("/path/iisintegration")]
         public async Task MiddlewareIgnoresShutdownGivenWrongPath(string path)
         {
-            var requestExecuted = new ManualResetEvent(false);
-            var applicationStoppingFired = new ManualResetEvent(false);
+            var requestExecuted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var applicationStoppingFired = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var builder = new WebHostBuilder()
                 .UseSetting("TOKEN", "TestToken")
                 .UseSetting("PORT", "12345")
@@ -177,11 +178,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
                 .Configure(app =>
                 {
                     var appLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
-                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.Set());
+                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.SetResult(0));
 
                     app.Run(context =>
                     {
-                        requestExecuted.Set();
+                        requestExecuted.SetResult(0);
                         return Task.FromResult(0);
                     });
                 });
@@ -192,8 +193,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
             request.Headers.TryAddWithoutValidation("MS-ASPNETCORE-EVENT", "shutdown");
             var response = await server.CreateClient().SendAsync(request);
 
-            Assert.False(applicationStoppingFired.WaitOne(TimeSpan.FromSeconds(1)));
-            Assert.True(requestExecuted.WaitOne(0));
+            Assert.False(applicationStoppingFired.Task.IsCompleted);
+            await requestExecuted.Task.TimeoutAfter(TimeSpan.FromSeconds(1));
             Assert.Equal(HttpStatusCode.OK, response.StatusCode);
         }
 
@@ -203,8 +204,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
         [InlineData(null)]
         public async Task MiddlewareIgnoresShutdownGivenWrongEvent(string shutdownEvent)
         {
-            var requestExecuted = new ManualResetEvent(false);
-            var applicationStoppingFired = new ManualResetEvent(false);
+            var requestExecuted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var applicationStoppingFired = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
             var builder = new WebHostBuilder()
                 .UseSetting("TOKEN", "TestToken")
                 .UseSetting("PORT", "12345")
@@ -213,11 +214,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
                 .Configure(app =>
                 {
                     var appLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
-                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.Set());
+                    appLifetime.ApplicationStopping.Register(() => applicationStoppingFired.SetResult(0));
 
                     app.Run(context =>
                     {
-                        requestExecuted.Set();
+                        requestExecuted.SetResult(0);
                         return Task.FromResult(0);
                     });
                 });
@@ -228,8 +229,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
             request.Headers.TryAddWithoutValidation("MS-ASPNETCORE-EVENT", shutdownEvent);
             var response = await server.CreateClient().SendAsync(request);
 
-            Assert.False(applicationStoppingFired.WaitOne(TimeSpan.FromSeconds(1)));
-            Assert.True(requestExecuted.WaitOne(0));
+            Assert.False(applicationStoppingFired.Task.IsCompleted);
+            await requestExecuted.Task.TimeoutAfter(TimeSpan.FromSeconds(1));
             Assert.Equal(HttpStatusCode.OK, response.StatusCode);
         }
 

+ 92 - 93
src/Tools/dotnet-watch/test/FileWatcherTests.cs

@@ -6,6 +6,8 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Testing;
 using Microsoft.DotNet.Watcher.Internal;
 using Xunit;
 using Xunit.Abstractions;
@@ -19,32 +21,33 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
             _output = output;
         }
 
-        private const int DefaultTimeout = 10 * 1000; // 10 sec
+        private readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);
+        private readonly TimeSpan NegativeTimeout = TimeSpan.FromSeconds(5);
         private readonly ITestOutputHelper _output;
 
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void NewFile(bool usePolling)
+        public async Task NewFile(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
-                using (var changedEv = new ManualResetEvent(false))
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                     var filesChanged = new HashSet<string>();
 
                     watcher.OnFileChange += (_, f) =>
                     {
                         filesChanged.Add(f);
-                        changedEv.Set();
+                        changedEv.TrySetResult(0);
                     };
                     watcher.EnableRaisingEvents = true;
 
                     var testFileFullPath = Path.Combine(dir, "foo");
                     File.WriteAllText(testFileFullPath, string.Empty);
 
-                    Assert.True(changedEv.WaitOne(DefaultTimeout));
+                    await changedEv.Task.TimeoutAfter(DefaultTimeout);
                     Assert.Equal(testFileFullPath, filesChanged.Single());
                 }
             });
@@ -53,16 +56,16 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void ChangeFile(bool usePolling)
+        public async Task ChangeFile(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 var testFileFullPath = Path.Combine(dir, "foo");
                 File.WriteAllText(testFileFullPath, string.Empty);
 
-                using (var changedEv = new ManualResetEvent(false))
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                     var filesChanged = new HashSet<string>();
 
                     EventHandler<string> handler = null;
@@ -72,7 +75,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                         watcher.OnFileChange -= handler;
 
                         filesChanged.Add(f);
-                        changedEv.Set();
+                        changedEv.TrySetResult(0);
                     };
 
                     watcher.OnFileChange += handler;
@@ -81,10 +84,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                     // On Unix the file write time is in 1s increments;
                     // if we don't wait, there's a chance that the polling
                     // watcher will not detect the change
-                    Thread.Sleep(1000);
+                    await Task.Delay(1000);
                     File.WriteAllText(testFileFullPath, string.Empty);
 
-                    Assert.True(changedEv.WaitOne(DefaultTimeout));
+                    await changedEv.Task.TimeoutAfter(DefaultTimeout);
                     Assert.Equal(testFileFullPath, filesChanged.Single());
                 }
             });
@@ -93,18 +96,18 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void MoveFile(bool usePolling)
+        public async Task MoveFile(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 var srcFile = Path.Combine(dir, "foo");
                 var dstFile = Path.Combine(dir, "foo2");
 
                 File.WriteAllText(srcFile, string.Empty);
 
-                using (var changedEv = new ManualResetEvent(false))
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                     var filesChanged = new HashSet<string>();
 
                     EventHandler<string> handler = null;
@@ -117,7 +120,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                             watcher.EnableRaisingEvents = false;
                             watcher.OnFileChange -= handler;
 
-                            changedEv.Set();
+                            changedEv.TrySetResult(0);
                         }
                     };
 
@@ -126,7 +129,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
 
                     File.Move(srcFile, dstFile);
 
-                    Assert.True(changedEv.WaitOne(DefaultTimeout));
+                    await changedEv.Task.TimeoutAfter(DefaultTimeout);
                     Assert.Contains(srcFile, filesChanged);
                     Assert.Contains(dstFile, filesChanged);
                 }
@@ -134,9 +137,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
         }
 
         [Fact]
-        public void FileInSubdirectory()
+        public async Task FileInSubdirectory()
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 var subdir = Path.Combine(dir, "subdir");
                 Directory.CreateDirectory(subdir);
@@ -144,9 +147,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                 var testFileFullPath = Path.Combine(subdir, "foo");
                 File.WriteAllText(testFileFullPath, string.Empty);
 
-                using (var changedEv = new ManualResetEvent(false))
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, true))
                 {
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                     var filesChanged = new HashSet<string>();
 
                     EventHandler<string> handler = null;
@@ -158,7 +161,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                         {
                             watcher.EnableRaisingEvents = false;
                             watcher.OnFileChange -= handler;
-                            changedEv.Set();
+                            changedEv.TrySetResult(0);
                         }
                     };
 
@@ -168,10 +171,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                     // On Unix the file write time is in 1s increments;
                     // if we don't wait, there's a chance that the polling
                     // watcher will not detect the change
-                    Thread.Sleep(1000);
+                    await Task.Delay(1000);
                     File.WriteAllText(testFileFullPath, string.Empty);
 
-                    Assert.True(changedEv.WaitOne(DefaultTimeout));
+                    await changedEv.Task.TimeoutAfter(DefaultTimeout);
                     Assert.Contains(subdir, filesChanged);
                     Assert.Contains(testFileFullPath, filesChanged);
                 }
@@ -181,14 +184,14 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void NoNotificationIfDisabled(bool usePolling)
+        public async Task NoNotificationIfDisabled(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
-                using (var changedEv = new ManualResetEvent(false))
                 {
-                    watcher.OnFileChange += (_, f) => changedEv.Set();
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+                    watcher.OnFileChange += (_, f) => changedEv.TrySetResult(0);
 
                     // Disable
                     watcher.EnableRaisingEvents = false;
@@ -198,10 +201,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                     // On Unix the file write time is in 1s increments;
                     // if we don't wait, there's a chance that the polling
                     // watcher will not detect the change
-                    Thread.Sleep(1000);
+                    await Task.Delay(1000);
                     File.WriteAllText(testFileFullPath, string.Empty);
 
-                    Assert.False(changedEv.WaitOne(DefaultTimeout / 2));
+                    await Assert.ThrowsAsync<TimeoutException>(() => changedEv.Task.TimeoutAfter(NegativeTimeout));
                 }
             });
         }
@@ -209,37 +212,35 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void DisposedNoEvents(bool usePolling)
+        public async Task DisposedNoEvents(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
-                using (var changedEv = new ManualResetEvent(false))
+                var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+                using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
-                    using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
-                    {
-                        watcher.OnFileChange += (_, f) => changedEv.Set();
-                        watcher.EnableRaisingEvents = true;
-                    }
+                    watcher.OnFileChange += (_, f) => changedEv.TrySetResult(0);
+                    watcher.EnableRaisingEvents = true;
+                }
 
-                    var testFileFullPath = Path.Combine(dir, "foo");
+                var testFileFullPath = Path.Combine(dir, "foo");
 
-                    // On Unix the file write time is in 1s increments;
-                    // if we don't wait, there's a chance that the polling
-                    // watcher will not detect the change
-                    Thread.Sleep(1000);
-                    File.WriteAllText(testFileFullPath, string.Empty);
+                // On Unix the file write time is in 1s increments;
+                // if we don't wait, there's a chance that the polling
+                // watcher will not detect the change
+                await Task.Delay(1000);
+                File.WriteAllText(testFileFullPath, string.Empty);
 
-                    Assert.False(changedEv.WaitOne(DefaultTimeout / 2));
-                }
+                await Assert.ThrowsAsync<TimeoutException>(() => changedEv.Task.TimeoutAfter(NegativeTimeout));
             });
         }
 
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void MultipleFiles(bool usePolling)
+        public async Task MultipleFiles(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 File.WriteAllText(Path.Combine(dir, "foo1"), string.Empty);
                 File.WriteAllText(Path.Combine(dir, "foo2"), string.Empty);
@@ -249,9 +250,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
 
                 var testFileFullPath = Path.Combine(dir, "foo3");
 
-                using (var changedEv = new ManualResetEvent(false))
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                     var filesChanged = new HashSet<string>();
 
                     EventHandler<string> handler = null;
@@ -260,7 +261,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                         watcher.EnableRaisingEvents = false;
                         watcher.OnFileChange -= handler;
                         filesChanged.Add(f);
-                        changedEv.Set();
+                        changedEv.TrySetResult(0);
                     };
 
                     watcher.OnFileChange += handler;
@@ -269,11 +270,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                     // On Unix the file write time is in 1s increments;
                     // if we don't wait, there's a chance that the polling
                     // watcher will not detect the change
-                    Thread.Sleep(1000);
+                    await Task.Delay(1000);
 
                     File.WriteAllText(testFileFullPath, string.Empty);
 
-                    Assert.True(changedEv.WaitOne(DefaultTimeout));
+                    await changedEv.Task.TimeoutAfter(DefaultTimeout);
                     Assert.Equal(testFileFullPath, filesChanged.Single());
                 }
             });
@@ -282,11 +283,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void MultipleTriggers(bool usePolling)
+        public async Task MultipleTriggers(bool usePolling)
         {
             var filesChanged = new HashSet<string>();
 
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
@@ -294,7 +295,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
 
                     for (var i = 0; i < 5; i++)
                     {
-                        AssertFileChangeRaisesEvent(dir, watcher);
+                        await AssertFileChangeRaisesEvent(dir, watcher);
                     }
 
                     watcher.EnableRaisingEvents = false;
@@ -302,55 +303,53 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
             });
         }
 
-        private void AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher)
+        private async Task AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher)
         {
-            using (var semaphoreSlim = new SemaphoreSlim(0))
+            var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var expectedPath = Path.Combine(directory, Path.GetRandomFileName());
+            EventHandler<string> handler = (object _, string f) =>
             {
-                var expectedPath = Path.Combine(directory, Path.GetRandomFileName());
-                EventHandler<string> handler = (object _, string f) =>
+                _output.WriteLine("File changed: " + f);
+                try
                 {
-                    _output.WriteLine("File changed: " + f);
-                    try
-                    {
-                        if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase))
-                        {
-                            semaphoreSlim.Release();
-                        }
-                    }
-                    catch (ObjectDisposedException)
+                    if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase))
                     {
-                        // There's a known race condition here:
-                        // even though we tell the watcher to stop raising events and we unsubscribe the handler
-                        // there might be in-flight events that will still process. Since we dispose the reset
-                        // event, this code will fail if the handler executes after Dispose happens.
+                        changedEv.TrySetResult(0);
                     }
-                };
-
-                File.AppendAllText(expectedPath, " ");
-
-                watcher.OnFileChange += handler;
-                try
-                {
-                    // On Unix the file write time is in 1s increments;
-                    // if we don't wait, there's a chance that the polling
-                    // watcher will not detect the change
-                    Thread.Sleep(1000);
-                    File.AppendAllText(expectedPath, " ");
-                    Assert.True(semaphoreSlim.Wait(DefaultTimeout), "Expected a file change event for " + expectedPath);
                 }
-                finally
+                catch (ObjectDisposedException)
                 {
-                    watcher.OnFileChange -= handler;
+                    // There's a known race condition here:
+                    // even though we tell the watcher to stop raising events and we unsubscribe the handler
+                    // there might be in-flight events that will still process. Since we dispose the reset
+                    // event, this code will fail if the handler executes after Dispose happens.
                 }
+            };
+
+            File.AppendAllText(expectedPath, " ");
+
+            watcher.OnFileChange += handler;
+            try
+            {
+                // On Unix the file write time is in 1s increments;
+                // if we don't wait, there's a chance that the polling
+                // watcher will not detect the change
+                await Task.Delay(1000);
+                File.AppendAllText(expectedPath, " ");
+                await changedEv.Task.TimeoutAfter(DefaultTimeout);
+            }
+            finally
+            {
+                watcher.OnFileChange -= handler;
             }
         }
 
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
-        public void DeleteSubfolder(bool usePolling)
+        public async Task DeleteSubfolder(bool usePolling)
         {
-            UsingTempDirectory(dir =>
+            await UsingTempDirectory(async dir =>
             {
                 var subdir = Path.Combine(dir, "subdir");
                 Directory.CreateDirectory(subdir);
@@ -363,9 +362,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                 File.WriteAllText(f2, string.Empty);
                 File.WriteAllText(f3, string.Empty);
 
-                using (var changedEv = new AutoResetEvent(false))
                 using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
                 {
+                    var changedEv = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                     var filesChanged = new HashSet<string>();
 
                     EventHandler<string> handler = null;
@@ -377,7 +376,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
                         {
                             watcher.EnableRaisingEvents = false;
                             watcher.OnFileChange -= handler;
-                            changedEv.Set();
+                            changedEv.TrySetResult(0);
                         }
                     };
 
@@ -386,7 +385,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
 
                     Directory.Delete(subdir, recursive: true);
 
-                    Assert.True(changedEv.WaitOne(DefaultTimeout));
+                    await changedEv.Task.TimeoutAfter(DefaultTimeout);
 
                     Assert.Contains(f1, filesChanged);
                     Assert.Contains(f2, filesChanged);
@@ -396,7 +395,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
             });
         }
 
-        private static void UsingTempDirectory(Action<string> action)
+        private static async Task UsingTempDirectory(Func<string, Task> func)
         {
             var tempFolder = Path.Combine(Path.GetTempPath(), $"{nameof(FileWatcherTests)}-{Guid.NewGuid().ToString("N")}");
             if (Directory.Exists(tempFolder))
@@ -408,7 +407,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
 
             try
             {
-                action(tempFolder);
+                await func(tempFolder);
             }
             finally
             {