CancellationToken не работает в ASP.NET Core Web API HostedServiceC#

Место общения программистов C#
Ответить
Anonymous
 CancellationToken не работает в ASP.NET Core Web API HostedService

Сообщение Anonymous »

У меня есть веб-API ASP.NET Core 8 с HostedService и контроллером. Контроллер использует интерфейс, который также реализует HostedService, чтобы службу можно было запустить, остановить или перезапустить. По умолчанию предполагается отсутствие вмешательства.
Метод StartAsync использует PeriodicTimer, поэтому попытка выполнения операции будет предприниматься каждые 15 секунд.
У меня проблема в том, что при попытке остановить службу она не останавливается. Я перепробовал все, что мог придумать, и надеялся увидеть, сможет ли кто-нибудь указать, что я делаю неправильно. Когда я говорю, что он не останавливается, после истечения тактов периодического таймера запускается другой цикл работы. CancellationToken должен быть потокобезопасным и при этом не распознавать отмену работы.
Код

Код: Выделить всё

Program.cs:
using HostedService.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddSingleton();
builder.Services.AddHostedService();

builder.Services.Configure(x =>
{
x.ServicesStartConcurrently = true;
x.ServicesStopConcurrently = false;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Код: Выделить всё

IControllableBackgroundService:
namespace HostedService.Services;

public interface IControllableBackgroundService
{
Task StartServiceAsync();
Task StopServiceAsync();
Task RestartServiceAsync();
}

Код: Выделить всё

BackgroundServiceController:
using HostedService.Services;
using Microsoft.AspNetCore.Mvc;

namespace HostedService.Controllers;
[ApiController]
[Route("[controller]")]
public class BackgroundServiceController(IControllableBackgroundService backgroundService) : ControllerBase
{
[HttpGet]
[Route("start")]
public async Task StartService()
{
await backgroundService.StartServiceAsync();
return Ok();
}

[HttpGet]
[Route("stop")]
public async Task StopService()
{
await backgroundService.StopServiceAsync();
return Ok();
}

[HttpGet]
[Route("restart")]
public async Task RestartService()
{
await backgroundService.RestartServiceAsync();
return Ok();
}
}

Код: Выделить всё

UserOfficeHostedService:
namespace HostedService.Services;

public class UserOfficeHostedService(
ILogger logger) : IHostedService, IControllableBackgroundService, IDisposable
{
private CancellationTokenSource cts = new();
private PeriodicTimer timer = new (TimeSpan.FromSeconds(15)));

public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("starting service");
cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

while (await timer.WaitForNextTickAsync(cts.Token))
{
if (cts.Token.IsCancellationRequested)
break;

await UpdateUserOfficeCacheAsync(cts.Token);
}
}

public async Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("stopping service");

await cts.CancelAsync();
timer.Dispose();
}

public async Task StartServiceAsync()
{
if (!cts.IsCancellationRequested)
return;

timer = new PeriodicTimer(TimeSpan.FromSeconds(serviceInterval));
await StartAsync(cts.Token);
}

public async Task StopServiceAsync()
{
await cts.CancelAsync();
await StopAsync(CancellationToken.None);
}

public async Task RestartServiceAsync()
{
await StopAsync(cts.Token);
timer = new PeriodicTimer(TimeSpan.FromSeconds(serviceInterval));
await StartAsync(cts.Token);
}

private async Task UpdateUserOfficeCacheAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Performing update on UserOffice Cache. Time: {DateTimeOffset.Now}");

try
{
if (cancellationToken.IsCancellationRequested)
{
logger.LogInformation($"Cancellation received before work attempted.  Time: {DateTimeOffset.Now}");
return;
}

// Simulate task
await Task.Delay(16000, cancellationToken); // Replace this with actual long-running task logic

if (cancellationToken.IsCancellationRequested)
{
logger.LogInformation($"Cancellation received after work started. Time: {DateTimeOffset.Now}");
}
}
catch (TaskCanceledException)
{
logger.LogInformation("UserOffice Cache update was canceled.");
}
finally
{
logger.LogInformation("UserOffice Background Service released semaphore.");
}
}

public void Dispose()
{
timer?.Dispose();
cts?.Dispose();
}
}
Результаты

Код: Выделить всё

info: HostedService.Services.UserOfficeHostedService[0]
starting service
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7016
info: HostedService.Services.UserOfficeHostedService[0]
Performing update on UserOffice Cache. Time: 7/16/2024 4:41:51 PM -04:00
info: HostedService.Services.UserOfficeHostedService[0]
stopping service
info: HostedService.Services.UserOfficeHostedService[0]
UserOffice Background Service released semaphore.
info: HostedService.Services.UserOfficeHostedService[0]
Performing update on UserOffice Cache. Time: 7/16/2024 4:42:07 PM -04:00
Как видите, служба была остановлена, а затем началась дополнительная работа. Как это исправить?

Подробнее здесь: https://stackoverflow.com/questions/787 ... tedservice
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»