offers the synchronous part of start and stop in IHostedService to run concurrently...
authorBadre BSAILA <54767641+pedrobsaila@users.noreply.github.com>
Wed, 3 May 2023 01:00:40 +0000 (03:00 +0200)
committerGitHub <noreply@github.com>
Wed, 3 May 2023 01:00:40 +0000 (18:00 -0700)
* 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

src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs
src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/DelegateHostedService.cs
src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostTests.cs

index fcc55b2f5d43ecf886c8e941a68275c37cca3c54..13b18f8e3cf1534d4abe0194bcdd8224265c8adc 100644 (file)
@@ -70,23 +70,22 @@ namespace Microsoft.Extensions.Hosting.Internal
 
             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
@@ -96,12 +95,7 @@ namespace Microsoft.Extensions.Hosting.Internal
                     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)
                     {
@@ -134,6 +128,16 @@ namespace Microsoft.Extensions.Hosting.Internal
             _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)
@@ -185,15 +189,22 @@ namespace Microsoft.Extensions.Hosting.Internal
 
                     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
index 6f6b319138f90b60fdf818f7884deea041e26f9f..370b4c40aa911622798912fea65fe24e94aaa40a 100644 (file)
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 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;
@@ -20,6 +20,8 @@ internal class DelegateHostedService : IHostedService, IDisposable
         _disposing = disposing;
     }
 
+    public int? Identifier { get; set; }
+
     public Task StartAsync(CancellationToken token)
     {
         StartDate = DateTimeOffset.Now;
@@ -37,4 +39,8 @@ internal class DelegateHostedService : IHostedService, IDisposable
 
     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}";
 }
index 4b678b8912b77e5257026d1d33ed362c1a36ed18..abda9052dffd64925fac724c8d976a98dd8f5991 100644 (file)
@@ -64,7 +64,7 @@ namespace Microsoft.Extensions.Hosting.Tests
             {
                 var index = i;
                 var service = new DelegateHostedService(() => { events[index, 0] = true; }, () => { events[index, 1] = true; } , () => { });
-
+                service.Identifier = index;
                 hostedServices[index] = service;
             }
 
@@ -92,8 +92,11 @@ namespace Microsoft.Extensions.Hosting.Tests
                 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);
 
@@ -103,8 +106,11 @@ namespace Microsoft.Extensions.Hosting.Tests
                 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]