-
-
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); | |
} | |
} |
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!
Thanks for providing this class :)
Two questions please:
Task.CompletedTask
is returned when_executingTask.IsCompleted
istrue
and_executingTask
is returned otherwise?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!