diff --git a/samples/GenericHostSample/MyServiceA.cs b/samples/GenericHostSample/MyServiceA.cs
index 93be796bbb3..d8f8c6f039d 100644
--- a/samples/GenericHostSample/MyServiceA.cs
+++ b/samples/GenericHostSample/MyServiceA.cs
@@ -5,38 +5,22 @@
namespace GenericHostSample
{
- public class MyServiceA : IHostedService
+ public class MyServiceA : BackgroundService
{
- private bool _stopping;
- private Task _backgroundTask;
-
- public Task StartAsync(CancellationToken cancellationToken)
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine("MyServiceA is starting.");
- _backgroundTask = BackgroundTask();
- return Task.CompletedTask;
- }
- private async Task BackgroundTask()
- {
- while (!_stopping)
+ stoppingToken.Register(() => Console.WriteLine("MyServiceA is stopping."));
+
+ while (!stoppingToken.IsCancellationRequested)
{
- await Task.Delay(TimeSpan.FromSeconds(5));
Console.WriteLine("MyServiceA is doing background work.");
+
+ await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
Console.WriteLine("MyServiceA background task is stopping.");
}
-
- public async Task StopAsync(CancellationToken cancellationToken)
- {
- Console.WriteLine("MyServiceA is stopping.");
- _stopping = true;
- if (_backgroundTask != null)
- {
- // TODO: cancellation
- await _backgroundTask;
- }
- }
}
}
diff --git a/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs b/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs
new file mode 100644
index 00000000000..65b2bf7e848
--- /dev/null
+++ b/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs
@@ -0,0 +1,75 @@
+// 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;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Hosting
+{
+ ///
+ /// Base class for implementing a long running .
+ ///
+ public abstract class BackgroundService : IHostedService, IDisposable
+ {
+ private Task _executingTask;
+ private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
+
+ ///
+ /// This method is called when the starts. The implementation should return a task that represents
+ /// the lifetime of the long running operation(s) being performed.
+ ///
+ /// Triggered when is called.
+ /// A that represents the long running operations.
+ protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
+
+ ///
+ /// Triggered when the application host is ready to start the service.
+ ///
+ /// Indicates that the start process has been aborted.
+ public virtual Task StartAsync(CancellationToken cancellationToken)
+ {
+ // Store the task we're executing
+ _executingTask = ExecuteAsync(_stoppingCts.Token);
+
+ // If the task is completed then return it, this will bubble cancellation and failure to the caller
+ if (_executingTask.IsCompleted)
+ {
+ return _executingTask;
+ }
+
+ // Otherwise it's running
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Triggered when the application host is performing a graceful shutdown.
+ ///
+ /// Indicates that the shutdown process should no longer be graceful.
+ public virtual async Task StopAsync(CancellationToken cancellationToken)
+ {
+ // Stop called without start
+ if (_executingTask == null)
+ {
+ return;
+ }
+
+ try
+ {
+ // Signal cancellation to the executing method
+ _stoppingCts.Cancel();
+ }
+ finally
+ {
+ // Wait until the task completes or the stop token triggers
+ await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
+ }
+
+ }
+
+ public virtual void Dispose()
+ {
+ _stoppingCts.Cancel();
+ }
+ }
+}
diff --git a/test/Microsoft.Extensions.Hosting.Tests/BackgroundHostedServiceTests.cs b/test/Microsoft.Extensions.Hosting.Tests/BackgroundHostedServiceTests.cs
new file mode 100644
index 00000000000..40b537fafb7
--- /dev/null
+++ b/test/Microsoft.Extensions.Hosting.Tests/BackgroundHostedServiceTests.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Extensions.Hosting.Tests
+{
+ public class BackgroundHostedServiceTests
+ {
+ [Fact]
+ public void StartReturnsCompletedTaskIfLongRunningTaskIsIncomplete()
+ {
+ var tcs = new TaskCompletionSource