-
-
Save davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3 to your computer and use it in GitHub Desktop.
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Hosting; | |
namespace WebApplication24 | |
{ | |
public abstract class HostedService : IHostedService | |
{ | |
private Task _executingTask; | |
private CancellationTokenSource _cts; | |
public Task StartAsync(CancellationToken cancellationToken) | |
{ | |
// Create a linked token so we can trigger cancellation outside of this token's cancellation | |
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | |
// Store the task we're executing | |
_executingTask = ExecuteAsync(_cts.Token); | |
// If the task is completed then return it | |
if (_executingTask.IsCompleted) | |
{ | |
return _executingTask; | |
} | |
// Otherwise it's running | |
return Task.CompletedTask; | |
} | |
public async Task StopAsync(CancellationToken cancellationToken) | |
{ | |
// Stop called without start | |
if (_executingTask == null) | |
{ | |
return; | |
} | |
// Signal cancellation to the executing method | |
_cts.Cancel(); | |
// Wait until the task completes or the stop token triggers | |
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken)); | |
// Throw if cancellation triggered | |
cancellationToken.ThrowIfCancellationRequested(); | |
} | |
// Derived classes should override this and execute a long running method until | |
// cancellation is requested | |
protected abstract Task ExecuteAsync(CancellationToken cancellationToken); | |
} | |
} |
@cwe1ss hey!
As the original token from StartAsync() would trigger immediately when CTRL+C is pressed. this would result in ExecuteAsync being cancelled even before someone calls StopAsync(), right?
The Token passed into StartAsync is to cancel startup (if it isn't that then we might have a problem) which makes it valid to cancel the entire process (even before StopAsync is called). One thing we haven't clarified is the behavior when StartAsync is cancelled, should it even call StopAsync?
@davidfowl Did we clarify ;-) ?
Hi David!
Thanks for providing this class :)
Two questions please:
- Shouldn't line 26 be switched with line 30, so
Task.CompletedTask
is returned when_executingTask.IsCompleted
istrue
and_executingTask
is returned otherwise? - Shouldn't
StartAsync
be marked with theasync
modifier (like you did withStopAsync
), and then lines 23-30 could apparently be replaced withawait _executingTask;
, and noreturn
statements will be needed, as shown in this MSDN's example?
Thanks!
Oh my, I think I got it. The return Task
by StartAsync
is the task of starting the internal _executingTask
. If _executingTask.IsCompleted
so fast, then it's probably due to a failure, and anyway it could be considered as if the task of starting _executingTask
failed, and the reason for the failure might be encapsulated withing _executingTask
. That's why when _executingTask.IsCompleted
, _executingTask
is returned. Otherwise, if _executingTask
is still running, then the task of starting _executingTask
has completed successfully and therefore Task.ComletedTask
is returned...
This think this might also answer my second question. Since StartAsync
doesn't really create a Task
for starting _executingTask
, it returns different instances of Task
, depending on whether starting _executingTask
succeeded or not. If it succeeded - some Task.ComletedTask
is returned; otherwise, the internal _executingTask
is returned, which is a bit of a trick to propagate the internal error that occurred in the attempt of starting _executingTask
. This forces to not use the async
modifier on StartAsync
, as using async
would inevitably return the first await
ed Task
, which would prevent using that trick.
When you get around to it, I'd love to know if I understood correctly.
Thanks!
Yair
@yaireclipse YEP!
Is cancellationToken.ThrowIfCancellationRequested(); in StopAsync necessary?
HostedServiceExecutor doesn't catch OperationCancelledException and we get a lot of errors in console
Application started. Press Ctrl+C to shut down.
Application is shutting down...
crit: Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor[10]
An error occurred stopping the application
System.AggregateException: One or more errors occurred. (The operation was canceled.) (The operation was canceled.) (The operation was canceled.) ---> System.OperationCanceledException: The operation was canceled.
Hi David, isn't the linked CTS a problem here? As the original token from StartAsync() would trigger immediately when CTRL+C is pressed, this would result in ExecuteAsync being cancelled even before someone calls StopAsync(), right?