Allow ExecuteAsync cancellation propagation (dotnet/Extensions#2823)
authorklettier <lettier.kevin@gmail.com>
Mon, 6 Jan 2020 17:01:55 +0000 (18:01 +0100)
committerChris Ross <Tratcher@Outlook.com>
Mon, 6 Jan 2020 17:01:55 +0000 (09:01 -0800)
Commit migrated from https://github.com/dotnet/Extensions/commit/941aa42767a16be8c0819e8a6f8c168c470526c8

src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs
src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundHostedServiceTests.cs

index 65b2bf7..00c1c3b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
@@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Hosting
     public abstract class BackgroundService : IHostedService, IDisposable
     {
         private Task _executingTask;
-        private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
+        private CancellationTokenSource _stoppingCts;
 
         /// <summary>
         /// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
@@ -29,6 +29,9 @@ namespace Microsoft.Extensions.Hosting
         /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
         public virtual Task StartAsync(CancellationToken cancellationToken)
         {
+            // Create linked token to allow cancelling executing task from provided token
+            _stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+
             // Store the task we're executing
             _executingTask = ExecuteAsync(_stoppingCts.Token);
 
@@ -69,7 +72,7 @@ namespace Microsoft.Extensions.Hosting
 
         public virtual void Dispose()
         {
-            _stoppingCts.Cancel();
+            _stoppingCts?.Cancel();
         }
     }
 }
index 40b537f..0f42f51 100644 (file)
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Threading;
@@ -106,11 +106,36 @@ namespace Microsoft.Extensions.Hosting.Tests
             service.Dispose();
         }
 
+        [Fact]
+        public async Task StartAsyncThenCancelShouldCancelExecutingTask()
+        {
+            var tokenSource = new CancellationTokenSource();
+
+            var service = new WaitForCancelledTokenService();
+
+            await service.StartAsync(tokenSource.Token);
+
+            tokenSource.Cancel();
+
+            await Assert.ThrowsAsync<TaskCanceledException>(() => service.ExecutingTask);
+        }
+
+        [Fact]
+        public void CreateAndDisposeShouldNotThrow()
+        {
+            var service = new WaitForCancelledTokenService();
+
+            service.Dispose();
+        }
+
         private class WaitForCancelledTokenService : BackgroundService
         {
+            public Task ExecutingTask { get; private set; }
+
             protected override Task ExecuteAsync(CancellationToken stoppingToken)
             {
-                return Task.Delay(Timeout.Infinite, stoppingToken);
+                ExecutingTask = Task.Delay(Timeout.Infinite, stoppingToken);
+                return ExecutingTask;
             }
         }