Skip to content

Commit 3e545d3

Browse files
committed
Make BackgroundService run ExecuteAsync as task
1 parent 4fea27c commit 3e545d3

File tree

2 files changed

+65
-23
lines changed

2 files changed

+65
-23
lines changed

src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public virtual Task StartAsync(CancellationToken cancellationToken)
4242
// Create linked token to allow cancelling executing task from provided token
4343
_stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
4444

45-
// Store the task we're executing
46-
_executeTask = ExecuteAsync(_stoppingCts.Token);
45+
// Execute all of ExecuteAsync as a background thread, and store the task we're executing so that we can wait for it later.
46+
_executeTask = Task.Run(async () => await ExecuteAsync(_stoppingCts.Token).ConfigureAwait(false), _stoppingCts.Token);
4747

4848
// If the task is completed then return it, this will bubble cancellation and failure to the caller
4949
if (_executeTask.IsCompleted)

src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundServiceTests.cs

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Hosting.Tests
1111
public class BackgroundServiceTests
1212
{
1313
[Fact]
14-
public void StartReturnsCompletedTaskIfLongRunningTaskIsIncomplete()
14+
public void StartReturnsCompletedTask()
1515
{
1616
var tcs = new TaskCompletionSource<object>();
1717
var service = new MyBackgroundService(tcs.Task);
@@ -26,28 +26,12 @@ public void StartReturnsCompletedTaskIfLongRunningTaskIsIncomplete()
2626
}
2727

2828
[Fact]
29-
public void StartReturnsCompletedTaskIfCancelled()
29+
public async Task StartCancelledThrowsTaskCanceledException()
3030
{
31-
var tcs = new TaskCompletionSource<object>();
32-
tcs.TrySetCanceled();
33-
var service = new MyBackgroundService(tcs.Task);
34-
35-
var task = service.StartAsync(CancellationToken.None);
36-
37-
Assert.True(task.IsCompleted);
38-
Assert.Same(task, service.ExecuteTask);
39-
}
40-
41-
[Fact]
42-
public async Task StartReturnsLongRunningTaskIfFailed()
43-
{
44-
var tcs = new TaskCompletionSource<object>();
45-
tcs.TrySetException(new Exception("fail!"));
46-
var service = new MyBackgroundService(tcs.Task);
47-
48-
var exception = await Assert.ThrowsAsync<Exception>(() => service.StartAsync(CancellationToken.None));
31+
var ct = new CancellationToken(true);
32+
var service = new WaitForCancelledTokenService();
4933

50-
Assert.Equal("fail!", exception.Message);
34+
await Assert.ThrowsAsync<TaskCanceledException>(() => service.StartAsync(ct));
5135
}
5236

5337
[Fact]
@@ -116,6 +100,7 @@ public async Task StartAsyncThenCancelShouldCancelExecutingTask()
116100
var service = new WaitForCancelledTokenService();
117101

118102
await service.StartAsync(tokenSource.Token);
103+
await service.WaitForExecuteTask;
119104

120105
tokenSource.Cancel();
121106

@@ -130,13 +115,48 @@ public void CreateAndDisposeShouldNotThrow()
130115
service.Dispose();
131116
}
132117

118+
[Fact]
119+
public async Task StartSynchronousAndStop()
120+
{
121+
var tokenSource = new CancellationTokenSource();
122+
var service = new MySynchronousBackgroundService();
123+
124+
// should not block the start thread;
125+
await service.StartAsync(tokenSource.Token);
126+
await service.WaitForExecuteTask;
127+
await service.StopAsync(CancellationToken.None);
128+
129+
Assert.True(service.WaitForEndExecuteTask.IsCompleted);
130+
}
131+
132+
[Fact]
133+
public async Task StartSynchronousExecuteShouldBeCancelable()
134+
{
135+
var tokenSource = new CancellationTokenSource();
136+
var service = new MySynchronousBackgroundService();
137+
138+
await service.StartAsync(tokenSource.Token);
139+
await service.WaitForExecuteTask;
140+
141+
tokenSource.Cancel();
142+
143+
await service.WaitForEndExecuteTask;
144+
}
145+
133146
private class WaitForCancelledTokenService : BackgroundService
134147
{
148+
private TaskCompletionSource<object> _waitForExecuteTask = new TaskCompletionSource<object>();
149+
135150
public Task ExecutingTask { get; private set; }
136151

152+
public Task WaitForExecuteTask => _waitForExecuteTask.Task;
153+
137154
protected override Task ExecuteAsync(CancellationToken stoppingToken)
138155
{
139156
ExecutingTask = Task.Delay(Timeout.Infinite, stoppingToken);
157+
158+
_waitForExecuteTask.TrySetResult(null);
159+
140160
return ExecutingTask;
141161
}
142162
}
@@ -191,5 +211,27 @@ private async Task ExecuteCore(CancellationToken stoppingToken)
191211
await task;
192212
}
193213
}
214+
215+
private class MySynchronousBackgroundService : BackgroundService
216+
{
217+
private TaskCompletionSource<object> _waitForExecuteTask = new TaskCompletionSource<object>();
218+
private TaskCompletionSource<object> _waitForEndExecuteTask = new TaskCompletionSource<object>();
219+
220+
public Task WaitForExecuteTask => _waitForExecuteTask.Task;
221+
public Task WaitForEndExecuteTask => _waitForEndExecuteTask.Task;
222+
223+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
224+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
225+
{
226+
_waitForExecuteTask.TrySetResult(null);
227+
while (!stoppingToken.IsCancellationRequested)
228+
{
229+
Thread.Sleep(100); // never await, just block the thread
230+
}
231+
_waitForEndExecuteTask.TrySetResult(null);
232+
}
233+
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
234+
235+
}
194236
}
195237
}

0 commit comments

Comments
 (0)