From a432e94f9e2aaa12c35a6b28d672852a236b8097 Mon Sep 17 00:00:00 2001 From: klettier Date: Mon, 6 Jan 2020 18:01:55 +0100 Subject: [PATCH] Allow ExecuteAsync cancellation propagation (dotnet/Extensions#2823) Commit migrated from https://github.com/dotnet/Extensions/commit/941aa42767a16be8c0819e8a6f8c168c470526c8 --- .../src/BackgroundService.cs | 9 ++++--- .../UnitTests/BackgroundHostedServiceTests.cs | 29 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs index 65b2bf7..00c1c3b 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs @@ -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; /// /// This method is called when the starts. The implementation should return a task that represents @@ -29,6 +29,9 @@ namespace Microsoft.Extensions.Hosting /// Indicates that the start process has been aborted. 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(); } } } diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundHostedServiceTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundHostedServiceTests.cs index 40b537f..0f42f51 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundHostedServiceTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundHostedServiceTests.cs @@ -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(() => 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; } } -- 2.7.4