Skip to content

Commit

Permalink
offers the synchronous part of start and stop in IHostedService to ru…
Browse files Browse the repository at this point in the history
…n concurrently (#85191)

* offers the synchronous part of start and stop in IHostedService to run concurrently

* pass cancellation token to Task.Run

* fix cancellation token  issue and eliminate a lambda
  • Loading branch information
pedrobsaila authored May 3, 2023
1 parent 8828b34 commit 6379ecb
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 24 deletions.
47 changes: 29 additions & 18 deletions src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,22 @@ public async Task StartAsync(CancellationToken cancellationToken = default)

if (_options.ServicesStartConcurrently)
{
Task tasks = Task.WhenAll(_hostedServices.Select(async service =>
List<Task> tasks = new List<Task>();

foreach (IHostedService hostedService in _hostedServices)
{
await service.StartAsync(combinedCancellationToken).ConfigureAwait(false);
tasks.Add(Task.Run(() => StartAndTryToExecuteAsync(hostedService, combinedCancellationToken), combinedCancellationToken));
}

if (service is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
}));
Task groupedTasks = Task.WhenAll(tasks);

try
{
await tasks.ConfigureAwait(false);
await groupedTasks.ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.AddRange(tasks.Exception?.InnerExceptions ?? new[] { ex }.AsEnumerable());
exceptions.AddRange(groupedTasks.Exception?.InnerExceptions ?? new[] { ex }.AsEnumerable());
}
}
else
Expand All @@ -96,12 +95,7 @@ public async Task StartAsync(CancellationToken cancellationToken = default)
try
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);

if (hostedService is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
await StartAndTryToExecuteAsync(hostedService, combinedCancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -134,6 +128,16 @@ public async Task StartAsync(CancellationToken cancellationToken = default)
_logger.Started();
}

private async Task StartAndTryToExecuteAsync(IHostedService service, CancellationToken combinedCancellationToken)
{
await service.StartAsync(combinedCancellationToken).ConfigureAwait(false);

if (service is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
}

private async Task TryExecuteBackgroundServiceAsync(BackgroundService backgroundService)
{
// backgroundService.ExecuteTask may not be set (e.g. if the derived class doesn't call base.StartAsync)
Expand Down Expand Up @@ -185,15 +189,22 @@ public async Task StopAsync(CancellationToken cancellationToken = default)

if (_options.ServicesStopConcurrently)
{
Task tasks = Task.WhenAll(hostedServices.Select(async service => await service.StopAsync(token).ConfigureAwait(false)));
List<Task> tasks = new List<Task>();

foreach (IHostedService hostedService in hostedServices)
{
tasks.Add(Task.Run(() => hostedService.StopAsync(token), token));
}

Task groupedTasks = Task.WhenAll(tasks);

try
{
await tasks.ConfigureAwait(false);
await groupedTasks.ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.AddRange(tasks.Exception?.InnerExceptions ?? new[] { ex }.AsEnumerable());
exceptions.AddRange(groupedTasks.Exception?.InnerExceptions ?? new[] { ex }.AsEnumerable());
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Microsoft.Extensions.Hosting.Unit.Tests;

internal class DelegateHostedService : IHostedService, IDisposable
internal class DelegateHostedService : IHostedService, IDisposable, IEquatable<DelegateHostedService>
{
private readonly Action _started;
private readonly Action _stopping;
Expand All @@ -20,6 +20,8 @@ public DelegateHostedService(Action started, Action stopping, Action disposing)
_disposing = disposing;
}

public int? Identifier { get; set; }

public Task StartAsync(CancellationToken token)
{
StartDate = DateTimeOffset.Now;
Expand All @@ -37,4 +39,8 @@ public Task StopAsync(CancellationToken token)

public DateTimeOffset StartDate { get; private set; }
public DateTimeOffset StopDate { get; private set; }

public bool Equals(DelegateHostedService other) => this == other;

public override string ToString() => $"DelegateHostedService: Id={Identifier}, StartDate={StartDate}, StopDate={StopDate}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public async Task StartAsync_StopAsync_Concurrency(bool stopConcurrently, bool s
{
var index = i;
var service = new DelegateHostedService(() => { events[index, 0] = true; }, () => { events[index, 1] = true; } , () => { });

service.Identifier = index;
hostedServices[index] = service;
}

Expand Down Expand Up @@ -92,8 +92,11 @@ public async Task StartAsync_StopAsync_Concurrency(bool stopConcurrently, bool s
Assert.False(events[i, 1]);
}

// Ensures that IHostedService instances are started in FIFO order
AssertExtensions.CollectionEqual(hostedServices, hostedServices.OrderBy(h => h.StartDate), EqualityComparer<DelegateHostedService>.Default);
// Ensures that IHostedService instances are started in FIFO order when services are started non concurrently
if (hostedServiceCount > 0 && !startConcurrently)
{
AssertExtensions.Equal(hostedServices, hostedServices.OrderBy(h => h.StartDate).ToArray());
}

await host.StopAsync(CancellationToken.None);

Expand All @@ -103,8 +106,11 @@ public async Task StartAsync_StopAsync_Concurrency(bool stopConcurrently, bool s
Assert.True(events[i, 1]);
}

// Ensures that IHostedService instances are stopped in LIFO order
AssertExtensions.CollectionEqual(hostedServices.Reverse(), hostedServices.OrderBy(h => h.StopDate), EqualityComparer<DelegateHostedService>.Default);
// Ensures that IHostedService instances are stopped in LIFO order when services are stopped non concurrently
if (hostedServiceCount > 0 && !stopConcurrently)
{
AssertExtensions.Equal(hostedServices, hostedServices.OrderByDescending(h => h.StopDate).ToArray());
}
}

[Fact]
Expand Down

0 comments on commit 6379ecb

Please sign in to comment.