@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Hosting.Tests
11
11
public class BackgroundServiceTests
12
12
{
13
13
[ Fact ]
14
- public void StartReturnsCompletedTaskIfLongRunningTaskIsIncomplete ( )
14
+ public void StartReturnsCompletedTask ( )
15
15
{
16
16
var tcs = new TaskCompletionSource < object > ( ) ;
17
17
var service = new MyBackgroundService ( tcs . Task ) ;
@@ -26,28 +26,14 @@ public void StartReturnsCompletedTaskIfLongRunningTaskIsIncomplete()
26
26
}
27
27
28
28
[ Fact ]
29
- public void StartReturnsCompletedTaskIfCancelled ( )
29
+ public async Task StartCancelledThrowsTaskCanceledException ( )
30
30
{
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 ) ;
31
+ var ct = new CancellationToken ( true ) ;
32
+ var service = new WaitForCancelledTokenService ( ) ;
47
33
48
- var exception = await Assert . ThrowsAsync < Exception > ( ( ) => service . StartAsync ( CancellationToken . None ) ) ;
34
+ await service . StartAsync ( ct ) ;
49
35
50
- Assert . Equal ( "fail!" , exception . Message ) ;
36
+ await Assert . ThrowsAnyAsync < OperationCanceledException > ( ( ) => service . ExecuteTask ) ;
51
37
}
52
38
53
39
[ Fact ]
@@ -91,6 +77,7 @@ public async Task StopAsyncThrowsIfCancellationCallbackThrows()
91
77
var service = new ThrowOnCancellationService ( ) ;
92
78
93
79
await service . StartAsync ( CancellationToken . None ) ;
80
+ await service . WaitForExecuteTask ;
94
81
95
82
var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 1 ) ) ;
96
83
await Assert . ThrowsAsync < AggregateException > ( ( ) => service . StopAsync ( cts . Token ) ) ;
@@ -116,6 +103,7 @@ public async Task StartAsyncThenCancelShouldCancelExecutingTask()
116
103
var service = new WaitForCancelledTokenService ( ) ;
117
104
118
105
await service . StartAsync ( tokenSource . Token ) ;
106
+ await service . WaitForExecuteTask ;
119
107
120
108
tokenSource . Cancel ( ) ;
121
109
@@ -130,19 +118,58 @@ public void CreateAndDisposeShouldNotThrow()
130
118
service . Dispose ( ) ;
131
119
}
132
120
121
+ [ Fact ]
122
+ public async Task StartSynchronousAndStop ( )
123
+ {
124
+ var tokenSource = new CancellationTokenSource ( ) ;
125
+ var service = new MySynchronousBackgroundService ( ) ;
126
+
127
+ // should not block the start thread;
128
+ await service . StartAsync ( tokenSource . Token ) ;
129
+ await service . WaitForExecuteTask ;
130
+ await service . StopAsync ( CancellationToken . None ) ;
131
+
132
+ Assert . True ( service . WaitForEndExecuteTask . IsCompleted ) ;
133
+ }
134
+
135
+ [ Fact ]
136
+ public async Task StartSynchronousExecuteShouldBeCancelable ( )
137
+ {
138
+ var tokenSource = new CancellationTokenSource ( ) ;
139
+ var service = new MySynchronousBackgroundService ( ) ;
140
+
141
+ await service . StartAsync ( tokenSource . Token ) ;
142
+ await service . WaitForExecuteTask ;
143
+
144
+ tokenSource . Cancel ( ) ;
145
+
146
+ await service . WaitForEndExecuteTask ;
147
+ }
148
+
133
149
private class WaitForCancelledTokenService : BackgroundService
134
150
{
151
+ private TaskCompletionSource < object > _waitForExecuteTask = new TaskCompletionSource < object > ( ) ;
152
+
135
153
public Task ExecutingTask { get ; private set ; }
136
154
155
+ public Task WaitForExecuteTask => _waitForExecuteTask . Task ;
156
+
137
157
protected override Task ExecuteAsync ( CancellationToken stoppingToken )
138
158
{
139
159
ExecutingTask = Task . Delay ( Timeout . Infinite , stoppingToken ) ;
160
+
161
+ _waitForExecuteTask . TrySetResult ( null ) ;
162
+
140
163
return ExecutingTask ;
141
164
}
142
165
}
143
166
144
167
private class ThrowOnCancellationService : BackgroundService
145
168
{
169
+ private TaskCompletionSource < object > _waitForExecuteTask = new TaskCompletionSource < object > ( ) ;
170
+
171
+ public Task WaitForExecuteTask => _waitForExecuteTask . Task ;
172
+
146
173
public int TokenCalls { get ; set ; }
147
174
148
175
protected override Task ExecuteAsync ( CancellationToken stoppingToken )
@@ -158,6 +185,8 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
158
185
TokenCalls ++ ;
159
186
} ) ;
160
187
188
+ _waitForExecuteTask . TrySetResult ( null ) ;
189
+
161
190
return new TaskCompletionSource < object > ( ) . Task ;
162
191
}
163
192
}
@@ -191,5 +220,24 @@ private async Task ExecuteCore(CancellationToken stoppingToken)
191
220
await task ;
192
221
}
193
222
}
223
+
224
+ private class MySynchronousBackgroundService : BackgroundService
225
+ {
226
+ private TaskCompletionSource < object > _waitForExecuteTask = new TaskCompletionSource < object > ( ) ;
227
+ private TaskCompletionSource < object > _waitForEndExecuteTask = new TaskCompletionSource < object > ( ) ;
228
+
229
+ public Task WaitForExecuteTask => _waitForExecuteTask . Task ;
230
+ public Task WaitForEndExecuteTask => _waitForEndExecuteTask . Task ;
231
+
232
+ #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
233
+ protected override async Task ExecuteAsync ( CancellationToken stoppingToken )
234
+ {
235
+ _waitForExecuteTask . TrySetResult ( null ) ;
236
+ stoppingToken . WaitHandle . WaitOne ( ) ;
237
+ _waitForEndExecuteTask . TrySetResult ( null ) ;
238
+ }
239
+ #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
240
+
241
+ }
194
242
}
195
243
}
0 commit comments