我创建了一个 ASP.NET Core Web API,但我不知道如何正确实现 IHostedService。我有几个需要作为后台进程运行的“worker”类,因此我使用 IHostedService 来异步启动所有任务。
启动.cs:
services.AddHostedService<BackgroundService>();
后台服务.cs:
public class BackgroundService: IHostedService
{
private CancellationTokenSource cts = new CancellationTokenSource();
public Task StartAsync(CancellationToken cancellationToken)
{
return RunTasks (cts.Token);
}
private List<IWorker> workersToRun = new List<IWorker>();
private Task RunTasks(CancellationToken cancellationToken)
{
try
{
Worker1 w1 = new Worker1(); //Implements IWorker
workersToRun.Add(Task.Run(() => w1.DoWork(cancellationToken)));
Worker1 w2 = new Worker2(); //Implements IWorker
workersToRun.Add(Task.Run(() => w2.DoWork(cancellationToken)));
Task.WhenAll(workersToRun.ToArray());
return Task.CompletedTask;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
throw;
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
try
{
cts.Cancel();
}
finally
{
//Wait for all started workers/tasks to complete ??????
}
return Task.CompletedTask;
}
public virtual void Dispose()
{
cts.Cancel();
cts.Dispose();
}
}
Worker.cs
public interface IWorker
{
Task DoWork(CancelationToken token)
}
public class Worker1 : IWorker
{
public Task DoWork(CancelationToken token)
{
while (!token.IsCancellationRequested)
{
return Task.Delay(1000);
//Do some random stuff in the background
}
//cleanup
}
}
public class Worker2 : IWorker
{
public async Task DoWork(CancelationToken token)
{
while (!token.IsCancellationRequested)
{
await Task.Delay(1000);
//Do some random async stuff in the background
}
//cleanup
}
}
worker 类似乎可以工作(基于日志),但 cancelationToken 不能工作,因此清理代码永远不会执行(看起来)。
如何在StopAsync方法中正确取消所有正在运行的任务并等待它们完成?
(上面的所有代码都是简化的,它实际上包含 DI 和错误处理,但这不相关)
您的代码有几个问题。
Task.Delay()
,并且很有可能您的工作人员将在这种方法中停留相当长一段时间。 (此外,我知道您可能会这样做,以便您可以测试取消,但按照编码,它会干扰您的测试)。cts.Token
给出的标记与 cancellationToken
的 BackgroundService
参数不同(很可能)。Task.WhenAll()
中执行 StartAsync
,当您对其应用 await
时,将阻止启动,直到所有任务实际完成。我认为这不是你想要的。//Do some random async stuff in the background
,包括将令牌传递到异步堆栈中。这里有一些代码可以消除上述问题。
public class BackgroundService : IHostedService
{
private readonly CancellationTokenSource cts;
public BackgroundService(CancellationTokenSource cts) => this.cts = cts;
public async Task StartAsync(CancellationToken cancellationToken) => await RunTasks (cancellationToken);
private List<Task> workersToRun = new List<Task>();
private async Task RunTasks(CancellationToken cancellationToken)
{
try
{
// tasks are started immediately below
var w1 = new Worker1();
workersToRun.Add(Task.Run(async () => await w1.DoWork(cancellationToken)));
var w2 = new Worker2();
workersToRun.Add(Task.Run(async () => await w2.DoWork(cancellationToken)));
// no Task.WhenAll() here. If you do that, RunTasks() will be blocked until they complete!
await Task.CompletedTask;
}
catch (Exception ex)
{
Program.WriteLog(ex.Message);
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// skip cancellation if we don't need it
if (workersToRun.All(x => x.IsCompleted))
return;
try
{
Program.WriteLog("Call Cancel()");
cts.Cancel();
}
finally
{
// wait for all started workers/tasks to complete
Program.WriteLog("WhenAll()");
await Task.WhenAll(workersToRun);
}
}
...
}
和其中一名工人,
public class Worker1 : IWorker
{
public async Task DoWork(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
await Task.Delay(1000, token);
}
catch (TaskCanceledException)
{
Program.WriteLog("Worker1 cancelled in Delay()");
break;
}
if (!token.IsCancellationRequested)
Program.WriteLog("Doing work in Worker1");
}
Program.WriteLog("Worker1 completed; clean up");
}
}
最后是司机:
class Program
{
public static List<string> log = new List<string>();
public static void WriteLog(string s)
{
lock (log) log.Add(s);
}
static async Task Main(string[] args)
{
WriteLog("Start");
var cts = new CancellationTokenSource();
var service = new BackgroundService(cts);
WriteLog("Call StartAsync()");
await service.StartAsync(cts.Token);
WriteLog("Wait 500ms");
await Task.Delay(1500);
WriteLog("Call StopAsync()");
await service.StopAsync(cts.Token);
WriteLog("Done");
log.ForEach(Console.WriteLine);
Console.ReadLine();
}
}
以下是您在第一次运行开始后 500 毫秒取消,然后在第二次运行 1500 毫秒取消时的结果(通过使用
StopAsync
提供的令牌调用 cts.Token
)。
您会注意到,第一次运行中没有完成任何工作,而第二次运行中则完成了“一个单位”的工作。
这是有道理的;在第一个中,您在真正的工作开始之前取消了,而在第二个中,您在完成一个工作单元后但在第二个单元之前取消了。
您已请求取消,但您没有等待足够长的时间让任务做出反应。 我会指定一个超时,以便拒绝取消的工作人员不会停止进程。
cts.Cancel();
Task.WaitAll(workersToRun.ToArray(), TimeSpan.FromSeconds(30));