Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenDieser Browser wird nicht mehr unterstützt.
Führen Sie ein Upgrade auf Microsoft Edge durch, um die neuesten Features, Sicherheitsupdates und den technischen Support zu nutzen.
Von Jeow Li Huan
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
In ASP.NET Core können Hintergrundtasks als gehostete Dienste implementiert werden. Ein gehosteter Dienst ist eine Klasse mit Logik für Hintergrundaufgaben, die die Schnittstelle IHostedService implementiert. In diesem Artikel sind drei Beispiel für gehostete Dienste enthalten:
Die ASP.NET Core-Vorlage „Workerdienst“ dient als Ausgangspunkt für das Schreiben von Dienstanwendungen mit langer Laufzeit. Eine aus der Workerdienstvorlage erstellte App gibt das Worker SDK in ihrer Projektdatei an:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Gehen Sie folgendermaßen vor, wenn Sie die Vorlage als Grundlage für eine Hosted Services-App verwenden möchten:
Eine App, die auf der Workerdienstvorlage basiert, verwendet das Microsoft.NET.Sdk.Worker
SDK und verfügt über einen expliziten Paketverweis auf das Microsoft.Extensions.Hosting-Paket. Sehen Sie sich dazu beispielsweise die Projektdatei der Beispiel-App (BackgroundTasksSample.csproj
) an.
Für Web-Apps, die das Microsoft.NET.Sdk.Web
SDK verwenden, wird auf das Microsoft.Extensions.Hosting-Paket implizit über das geteilte Framework verwiesen. Es ist kein expliziter Paketverweis in der Projektdatei der App erforderlich.
Die IHostedService-Schnittstelle definiert zwei Methoden für Objekte, die vom Host verwaltet werden:
StartAsync(CancellationToken) enthält die Logik zum Starten der Hintergrundaufgabe. StartAsync
wird vor folgenden Vorgängen aufgerufen:
StartAsync
sollte auf Aufgaben mit kurzer Ausführung beschränkt werden, da gehostete Dienste sequenziell ausgeführt werden, sodass weitere Dienste erst gestartet werden, wenn die Ausführung von StartAsync
beendet wurde.
StopAsync
enthält die Logik zum Beenden des Hintergrundtasks. Implementieren Sie IDisposable und Finalizer (Destruktoren), um nicht verwaltete Ressourcen zu löschen.Das Abbruchtoken hat standardmäßig ein Zeitlimit von 30 Sekunden, um zu melden, dass der Prozess des Herunterfahrens nicht mehr ordnungsgemäß ausgeführt wird. Gehen Sie wie folgt vor, wenn ein Abbruch für das Token angefordert wird:
StopAsync
aufgerufen werden, sollten umgehend zurückgegeben werden.Allerdings werden keine Aufgaben abgebrochen, wenn der Abbruch angefordert wird. Der Aufrufer wartet, bis alle Aufgaben abgeschlossen sind.
Wenn die App unerwartet beendet wird (weil der Prozess der App beispielsweise fehlschlägt), wird StopAsync
möglicherweise nicht aufgerufen. Daher werden die in StopAsync
aufgerufenen Methoden oder ausgeführten Operationen nicht durchgeführt.
Um das standardmäßig 30-sekündige Timeout beim Herunterfahren zu verlängern, legen Sie Folgendes fest:
Der gehostete Dienst wird beim Start der App einmal aktiviert und beim Beenden der App wieder ordnungsgemäß heruntergefahren. Wenn während der Ausführung von Hintergrundtasks ein Fehler ausgelöst wird, sollte Dispose
aufgerufen werden, auch wenn StopAsync
nicht aufgerufen wird.
BackgroundService ist eine Basisklasse zur Implementierung eines IHostedService mit langer Laufzeit.
ExecuteAsync (CancellationToken) wird aufgerufen, um den Hintergrunddienst auszuführen. Die Implementierung gibt einen Task zurück, der die gesamte Lebensdauer des Hintergrunddiensts darstellt. Es werden keine weiteren Dienste gestartet, bis ExecuteAsync asynchron wird, etwa durch den Aufruf von await
. Vermeiden Sie die Ausführung von langen, blockierenden Initialisierungsarbeiten in ExecuteAsync
. Die Hostblöcke in StopAsync(CancellationToken) warten auf den Abschluss von ExecuteAsync
.
Das Abbruchtoken wird beim Aufruf von IHostedService.StopAsync ausgelöst. Ihre Implementierung von ExecuteAsync
sollte unverzüglich beendet werden, wenn das Abbruchtoken ausgelöst wird, um den Dienst ordnungsgemäß herunterzufahren. Andernfalls wird der Dienst beim Erreichen des Timeouts beim Herunterfahren nicht ordnungsgemäß beendet. Weitere Informationen finden Sie im Abschnitt IHostedService-Schnittstelle.
Weitere Informationen finden Sie im Quellcode für BackgroundService.
Zeitlich festgelegte Hintergrundtasks verwenden die Klasse System.Threading.Timer. Der Timer löst die DoWork
-Methode des Tasks aus. Der Timer wird durch StopAsync
deaktiviert und freigegeben, wenn der Dienstcontainer durch Dispose
freigegeben ist:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Timer wartet nicht auf den Abschluss vorheriger Ausführungen von DoWork
. Die veranschaulichte Vorgehensweise eignet sich also möglicherweise nicht für alle Szenarios. Interlocked.Increment wird zum Erhöhen des Ausführungszählers mit einem atomischen Vorgang verwendet, wodurch sichergestellt wird, dass executionCount
nicht durch mehrere Threads gleichzeitig aktualisiert wird.
Der Dienst wird in IHostBuilder.ConfigureServices
(Program.cs
) mit der Erweiterungsmethode AddHostedService
registriert:
services.AddHostedService<TimedHostedService>();
Erstellen Sie einen Bereich, um bereichsbezogene Dienste in einem BackgroundService zu verwenden. Bereiche werden für einen gehosteten Dienst nicht standardmäßig erstellt.
Der bereichsbezogene Dienst für Hintergrundtasks enthält die Logik des Hintergrundtasks. Im folgenden Beispiel:
DoWork
-Methode gibt Task
zurück. Zu Demonstrationszwecken wird in der DoWork
-Methode eine Verzögerung von zehn Sekunden verwendet.internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
Der gehostete Dienst erstellt einen Bereich, um den bereichsbezogenen Dienst für Hintergrundtasks aufzulösen, damit die DoWork
-Methode aufgerufen wird. DoWork
gibt einen Task
zurück, auf den in ExecuteAsync
gewartet wird:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Die Dienste werden in IHostBuilder.ConfigureServices
(Program.cs
) registriert. Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService
registriert:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Eine Warteschlange für Hintergrundaufgaben basiert auf dem .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
Im folgenden Beispiel für QueueHostedService
gilt:
BackgroundProcessing
-Methode gibt einen Task
zurück, auf den in ExecuteAsync
gewartet wird.BackgroundProcessing
ausgeführt.StopAsync
angehalten wird.public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Ein MonitorLoop
-Dienst verarbeitet das Einreihen von Tasks in die Warteschlange für den gehosteten Dienst, wenn der w
-Schlüssel auf einem Eingabegerät ausgewählt wird:
IBackgroundTaskQueue
wird in den MonitorLoop
-Dienst eingefügt.IBackgroundTaskQueue.QueueBackgroundWorkItem
wird aufgerufen, um ein Arbeitselement in die Warteschlange einzureihen.Task.Delay
).try-catch
-Anweisung fängt OperationCanceledException auf, wenn der Task abgebrochen wird.public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. "
+ "{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Die Dienste werden in IHostBuilder.ConfigureServices
(Program.cs
) registriert. Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService
registriert:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop
wird in Program.cs
gestartet:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Mit dem folgenden Code wird eine asynchron terminierte Hintergrundaufgabe erstellt:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Die Workerdienstvorlagen unterstützen die native AOT-Option (ahead-of-time) von .NET mit dem --aot
-Flag:
Die AOT-Option fügt der Projektdatei <PublishAot>true</PublishAot>
hinzu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
+ <PublishAot>true</PublishAot>
<UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
</ItemGroup>
</Project>
In ASP.NET Core können Hintergrundtasks als gehostete Dienste implementiert werden. Ein gehosteter Dienst ist eine Klasse mit Logik für Hintergrundaufgaben, die die Schnittstelle IHostedService implementiert. In diesem Artikel sind drei Beispiel für gehostete Dienste enthalten:
Die ASP.NET Core-Vorlage „Workerdienst“ dient als Ausgangspunkt für das Schreiben von Dienstanwendungen mit langer Laufzeit. Eine aus der Workerdienstvorlage erstellte App gibt das Worker SDK in ihrer Projektdatei an:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Gehen Sie folgendermaßen vor, wenn Sie die Vorlage als Grundlage für eine Hosted Services-App verwenden möchten:
Eine App, die auf der Workerdienstvorlage basiert, verwendet das Microsoft.NET.Sdk.Worker
SDK und verfügt über einen expliziten Paketverweis auf das Microsoft.Extensions.Hosting-Paket. Sehen Sie sich dazu beispielsweise die Projektdatei der Beispiel-App (BackgroundTasksSample.csproj
) an.
Für Web-Apps, die das Microsoft.NET.Sdk.Web
SDK verwenden, wird auf das Microsoft.Extensions.Hosting-Paket implizit über das geteilte Framework verwiesen. Es ist kein expliziter Paketverweis in der Projektdatei der App erforderlich.
Die IHostedService-Schnittstelle definiert zwei Methoden für Objekte, die vom Host verwaltet werden:
StartAsync(CancellationToken) enthält die Logik zum Starten der Hintergrundaufgabe. StartAsync
wird vor folgenden Vorgängen aufgerufen:
StartAsync
sollte auf Aufgaben mit kurzer Ausführung beschränkt werden, da gehostete Dienste sequenziell ausgeführt werden, sodass weitere Dienste erst gestartet werden, wenn die Ausführung von StartAsync
beendet wurde.
StopAsync
enthält die Logik zum Beenden des Hintergrundtasks. Implementieren Sie IDisposable und Finalizer (Destruktoren), um nicht verwaltete Ressourcen zu löschen.Das Abbruchtoken hat standardmäßig ein Zeitlimit von 30 Sekunden, um zu melden, dass der Prozess des Herunterfahrens nicht mehr ordnungsgemäß ausgeführt wird. Gehen Sie wie folgt vor, wenn ein Abbruch für das Token angefordert wird:
StopAsync
aufgerufen werden, sollten umgehend zurückgegeben werden.Allerdings werden keine Aufgaben abgebrochen, wenn der Abbruch angefordert wird. Der Aufrufer wartet, bis alle Aufgaben abgeschlossen sind.
Wenn die App unerwartet beendet wird (weil der Prozess der App beispielsweise fehlschlägt), wird StopAsync
möglicherweise nicht aufgerufen. Daher werden die in StopAsync
aufgerufenen Methoden oder ausgeführten Operationen nicht durchgeführt.
Um das standardmäßig 30-sekündige Timeout beim Herunterfahren zu verlängern, legen Sie Folgendes fest:
Der gehostete Dienst wird beim Start der App einmal aktiviert und beim Beenden der App wieder ordnungsgemäß heruntergefahren. Wenn während der Ausführung von Hintergrundtasks ein Fehler ausgelöst wird, sollte Dispose
aufgerufen werden, auch wenn StopAsync
nicht aufgerufen wird.
BackgroundService ist eine Basisklasse zur Implementierung eines IHostedService mit langer Laufzeit.
ExecuteAsync (CancellationToken) wird aufgerufen, um den Hintergrunddienst auszuführen. Die Implementierung gibt einen Task zurück, der die gesamte Lebensdauer des Hintergrunddiensts darstellt. Es werden keine weiteren Dienste gestartet, bis ExecuteAsync asynchron wird, etwa durch den Aufruf von await
. Vermeiden Sie die Ausführung von langen, blockierenden Initialisierungsarbeiten in ExecuteAsync
. Die Hostblöcke in StopAsync(CancellationToken) warten auf den Abschluss von ExecuteAsync
.
Das Abbruchtoken wird beim Aufruf von IHostedService.StopAsync ausgelöst. Ihre Implementierung von ExecuteAsync
sollte unverzüglich beendet werden, wenn das Abbruchtoken ausgelöst wird, um den Dienst ordnungsgemäß herunterzufahren. Andernfalls wird der Dienst beim Erreichen des Timeouts beim Herunterfahren nicht ordnungsgemäß beendet. Weitere Informationen finden Sie im Abschnitt IHostedService-Schnittstelle.
Weitere Informationen finden Sie im Quellcode für BackgroundService.
Zeitlich festgelegte Hintergrundtasks verwenden die Klasse System.Threading.Timer. Der Timer löst die DoWork
-Methode des Tasks aus. Der Timer wird durch StopAsync
deaktiviert und freigegeben, wenn der Dienstcontainer durch Dispose
freigegeben ist:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Timer wartet nicht auf den Abschluss vorheriger Ausführungen von DoWork
. Die veranschaulichte Vorgehensweise eignet sich also möglicherweise nicht für alle Szenarios. Interlocked.Increment wird zum Erhöhen des Ausführungszählers mit einem atomischen Vorgang verwendet, wodurch sichergestellt wird, dass executionCount
nicht durch mehrere Threads gleichzeitig aktualisiert wird.
Der Dienst wird in IHostBuilder.ConfigureServices
(Program.cs
) mit der Erweiterungsmethode AddHostedService
registriert:
services.AddHostedService<TimedHostedService>();
Erstellen Sie einen Bereich, um bereichsbezogene Dienste in einem BackgroundService zu verwenden. Bereiche werden für einen gehosteten Dienst nicht standardmäßig erstellt.
Der bereichsbezogene Dienst für Hintergrundtasks enthält die Logik des Hintergrundtasks. Im folgenden Beispiel:
DoWork
-Methode gibt Task
zurück. Zu Demonstrationszwecken wird in der DoWork
-Methode eine Verzögerung von zehn Sekunden verwendet.internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
Der gehostete Dienst erstellt einen Bereich, um den bereichsbezogenen Dienst für Hintergrundtasks aufzulösen, damit die DoWork
-Methode aufgerufen wird. DoWork
gibt einen Task
zurück, auf den in ExecuteAsync
gewartet wird:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Die Dienste werden in IHostBuilder.ConfigureServices
(Program.cs
) registriert. Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService
registriert:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Eine Warteschlange für Hintergrundaufgaben basiert auf dem .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
Im folgenden Beispiel für QueueHostedService
gilt:
BackgroundProcessing
-Methode gibt einen Task
zurück, auf den in ExecuteAsync
gewartet wird.BackgroundProcessing
ausgeführt.StopAsync
angehalten wird.public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Ein MonitorLoop
-Dienst verarbeitet das Einreihen von Tasks in die Warteschlange für den gehosteten Dienst, wenn der w
-Schlüssel auf einem Eingabegerät ausgewählt wird:
IBackgroundTaskQueue
wird in den MonitorLoop
-Dienst eingefügt.IBackgroundTaskQueue.QueueBackgroundWorkItem
wird aufgerufen, um ein Arbeitselement in die Warteschlange einzureihen.Task.Delay
).try-catch
-Anweisung fängt OperationCanceledException auf, wenn der Task abgebrochen wird.public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. "
+ "{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Die Dienste werden in IHostBuilder.ConfigureServices
(Program.cs
) registriert. Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService
registriert:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop
wird in Program.cs
gestartet:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Mit dem folgenden Code wird eine asynchron terminierte Hintergrundaufgabe erstellt:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
In ASP.NET Core können Hintergrundtasks als gehostete Dienste implementiert werden. Ein gehosteter Dienst ist eine Klasse mit Logik für Hintergrundaufgaben, die die Schnittstelle IHostedService implementiert. In diesem Artikel sind drei Beispiel für gehostete Dienste enthalten:
Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Die ASP.NET Core-Vorlage „Workerdienst“ dient als Ausgangspunkt für das Schreiben von Dienstanwendungen mit langer Laufzeit. Eine aus der Workerdienstvorlage erstellte App gibt das Worker SDK in ihrer Projektdatei an:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Gehen Sie folgendermaßen vor, wenn Sie die Vorlage als Grundlage für eine Hosted Services-App verwenden möchten:
Eine App, die auf der Workerdienstvorlage basiert, verwendet das Microsoft.NET.Sdk.Worker
SDK und verfügt über einen expliziten Paketverweis auf das Microsoft.Extensions.Hosting-Paket. Sehen Sie sich dazu beispielsweise die Projektdatei der Beispiel-App (BackgroundTasksSample.csproj
) an.
Für Web-Apps, die das Microsoft.NET.Sdk.Web
SDK verwenden, wird auf das Microsoft.Extensions.Hosting-Paket implizit über das geteilte Framework verwiesen. Es ist kein expliziter Paketverweis in der Projektdatei der App erforderlich.
Die IHostedService-Schnittstelle definiert zwei Methoden für Objekte, die vom Host verwaltet werden:
StartAsync
enthält die Logik zum Starten des Hintergrundtasks. StartAsync
wird vor folgenden Vorgängen aufgerufen:
Das Standardverhalten kann so geändert werden, dass der StartAsync
-Vorgang des gehosteten Diensts ausgeführt wird, nachdem die Pipeline der App konfiguriert und ApplicationStarted
aufgerufen wurde. Um das Standardverhalten zu ändern, fügen Sie den gehosteten Dienst (VideosWatcher
im folgenden Beispiel) nach dem Aufruf von ConfigureWebHostDefaults
hinzu:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddHostedService<VideosWatcher>();
});
}
StopAsync
enthält die Logik zum Beenden des Hintergrundtasks. Implementieren Sie IDisposable und Finalizer (Destruktoren), um nicht verwaltete Ressourcen zu löschen.Das Abbruchtoken hat standardmäßig ein Zeitlimit von fünf Sekunden, um zu melden, dass der Prozess des Herunterfahrens nicht mehr ordnungsgemäß ausgeführt wird. Gehen Sie wie folgt vor, wenn ein Abbruch für das Token angefordert wird:
StopAsync
aufgerufen werden, sollten umgehend zurückgegeben werden.Allerdings werden keine Aufgaben abgebrochen, wenn der Abbruch angefordert wird. Der Aufrufer wartet, bis alle Aufgaben abgeschlossen sind.
Wenn die App unerwartet beendet wird (weil der Prozess der App beispielsweise fehlschlägt), wird StopAsync
möglicherweise nicht aufgerufen. Daher werden die in StopAsync
aufgerufenen Methoden oder ausgeführten Operationen nicht durchgeführt.
Um das standardmäßig 5-sekündige Timeout beim Herunterfahren zu verlängern, legen Sie folgendes fest:
Der gehostete Dienst wird beim Start der App einmal aktiviert und beim Beenden der App wieder ordnungsgemäß heruntergefahren. Wenn während der Ausführung von Hintergrundtasks ein Fehler ausgelöst wird, sollte Dispose
aufgerufen werden, auch wenn StopAsync
nicht aufgerufen wird.
BackgroundService ist eine Basisklasse zur Implementierung eines IHostedService mit langer Laufzeit.
ExecuteAsync (CancellationToken) wird aufgerufen, um den Hintergrunddienst auszuführen. Die Implementierung gibt einen Task zurück, der die gesamte Lebensdauer des Hintergrunddiensts darstellt. Es werden keine weiteren Dienste gestartet, bis ExecuteAsync asynchron wird, etwa durch den Aufruf von await
. Vermeiden Sie die Ausführung von langen, blockierenden Initialisierungsarbeiten in ExecuteAsync
. Die Hostblöcke in StopAsync(CancellationToken) warten auf den Abschluss von ExecuteAsync
.
Das Abbruchtoken wird beim Aufruf von IHostedService.StopAsync ausgelöst. Ihre Implementierung von ExecuteAsync
sollte unverzüglich beendet werden, wenn das Abbruchtoken ausgelöst wird, um den Dienst ordnungsgemäß herunterzufahren. Andernfalls wird der Dienst beim Erreichen des Timeouts beim Herunterfahren nicht ordnungsgemäß beendet. Weitere Informationen finden Sie im Abschnitt IHostedService-Schnittstelle.
StartAsync
sollte auf Aufgaben mit kurzer Ausführung beschränkt werden, da gehostete Dienste sequenziell ausgeführt werden, sodass weitere Dienste erst gestartet werden, wenn die Ausführung von StartAsync
beendet wurde. Zeitintensive Aufgaben sollten in platziert ExecuteAsync
werden. Weitere Informationen finden Sie im Quellcode für BackgroundService.
Zeitlich festgelegte Hintergrundtasks verwenden die Klasse System.Threading.Timer. Der Timer löst die DoWork
-Methode des Tasks aus. Der Timer wird durch StopAsync
deaktiviert und freigegeben, wenn der Dienstcontainer durch Dispose
freigegeben ist:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Timer wartet nicht auf den Abschluss vorheriger Ausführungen von DoWork
. Die veranschaulichte Vorgehensweise eignet sich also möglicherweise nicht für alle Szenarios. Interlocked.Increment wird zum Erhöhen des Ausführungszählers mit einem atomischen Vorgang verwendet, wodurch sichergestellt wird, dass executionCount
nicht durch mehrere Threads gleichzeitig aktualisiert wird.
Der Dienst wird in IHostBuilder.ConfigureServices
(Program.cs
) mit der Erweiterungsmethode AddHostedService
registriert:
services.AddHostedService<TimedHostedService>();
Erstellen Sie einen Bereich, um bereichsbezogene Dienste in einem BackgroundService zu verwenden. Bereiche werden für einen gehosteten Dienst nicht standardmäßig erstellt.
Der bereichsbezogene Dienst für Hintergrundtasks enthält die Logik des Hintergrundtasks. Im folgenden Beispiel:
DoWork
-Methode gibt Task
zurück. Zu Demonstrationszwecken wird in der DoWork
-Methode eine Verzögerung von zehn Sekunden verwendet.internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
Der gehostete Dienst erstellt einen Bereich, um den bereichsbezogenen Dienst für Hintergrundtasks aufzulösen, damit die DoWork
-Methode aufgerufen wird. DoWork
gibt einen Task
zurück, auf den in ExecuteAsync
gewartet wird:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Die Dienste werden in IHostBuilder.ConfigureServices
(Program.cs
) registriert. Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService
registriert:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Eine Warteschlange für Hintergrundaufgaben basiert auf dem .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
Im folgenden Beispiel für QueueHostedService
gilt:
BackgroundProcessing
-Methode gibt einen Task
zurück, auf den in ExecuteAsync
gewartet wird.BackgroundProcessing
ausgeführt.StopAsync
angehalten wird.public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Ein MonitorLoop
-Dienst verarbeitet das Einreihen von Tasks in die Warteschlange für den gehosteten Dienst, wenn der w
-Schlüssel auf einem Eingabegerät ausgewählt wird:
IBackgroundTaskQueue
wird in den MonitorLoop
-Dienst eingefügt.IBackgroundTaskQueue.QueueBackgroundWorkItem
wird aufgerufen, um ein Arbeitselement in die Warteschlange einzureihen.Task.Delay
).try-catch
-Anweisung fängt OperationCanceledException auf, wenn der Task abgebrochen wird.public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Die Dienste werden in IHostBuilder.ConfigureServices
(Program.cs
) registriert. Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService
registriert:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop
wird in Program.Main
gestartet:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Feedback zu ASP.NET Core
ASP.NET Core ist ein Open Source-Projekt. Wählen Sie einen Link aus, um Feedback zu geben:
Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenTraining
Lernpfad
Erstellen von cloudnativen Apps und Diensten mit .NET und ASP.NET Core - Training
Erstellen Sie mit der kostenlosen .NET-Open-Source-Plattform resiliente und äußerst skalierbare Apps und Dienste, die sich unabhängig bereitstellen lassen. Mit .NET können Sie beliebte Microservicetechnologien wie Docker, Kubernetes, Dapr, Azure Container Registry und mehr für .NET- und ASP.NET Core-Anwendungen und -Dienste verwenden.
Dokumentation
Verwenden bereichsbezogener Dienste in einem BackgroundService - .NET
Erfahren Sie, wie Sie bereichsbezogene Dienste innerhalb eines BackgroundService in .NET verwenden können.
.NET-Microservicearchitektur für .NET-Containeranwendungen | Übersicht über neue Optionen zum Implementieren von Hintergrundtasks in Microservices in .NET Core mit IHostedService und BackgroundService
Breaking Change für .NET 6: Ausnahmebehandlung beim Hosting - .NET
Hier erhalten Sie Informationen zu einem Breaking Change für .NET 6 in .NET-Kernbibliotheken: Ausnahmefehler der Klasse „BackgroundService“ gehen nicht mehr verloren, sondern werden protokolliert.
Implementieren der IHostedService-Schnittstelle - .NET
Erfahren Sie, wie Sie eine benutzerdefinierte IHostedService-Schnittstelle ähnlich wie den integrierten .NET BackgroundService in C# implementieren.