在 IHostedService 中启动和停止任务
Start and stop tasks in IHostedService
我已经创建了一个 ASP.NET 核心 API 但我不知道如何正确地实现 IHostedService。
我有几个“工人”类需要运行作为后台进程,所以我使用 IHostedService 异步启动所有任务。
Startup.cs:
services.AddHostedService<BackgroundService>();
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 方法中正确取消所有 运行ning 任务并等待它们完成?
(上面的所有代码都是简化的,它实际上包含了 di 和错误处理,但那是不相关的)
您已请求取消,但您没有等待足够长的时间让任务对其做出反应。我会指定一个超时时间,这样拒绝取消的工作人员就不会停止进程。
cts.Cancel();
Task.WaitAll(workersToRun.ToArray(), TimeSpan.FromSeconds(30));
您的代码有几个问题。
- 您没有将取消令牌传递给
Task.Delay()
,您的工作人员很可能会在这种方法中停留很长一段时间。 (另外,我知道您可能会延迟,以便您可以测试取消,但编码会干扰您的测试)。
cts.Token
给出的令牌与 BackgroundService
的 cancellationToken
参数不同(很有可能)。
- 您正在
StartAsync
中执行 Task.WhenAll()
,当您对其应用 await
时,它将阻止启动,直到所有任务实际完成。我不认为那是你想要的。
- 取消是合作的,因此您需要在延迟后立即检查取消。当您“真正”删除延迟时,请务必在将要实现的任何内容中添加取消检查
//Do some random async stuff in the background
,包括将令牌传递到您的异步堆栈中。
- 最后,我建议async/await“一路走下去”。
下面是一些消除上述问题的代码。
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");
}
}
最后 driver:
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 毫秒后的结果(通过使用 [=14 提供的令牌调用 StopAsync
=]).
您会注意到第一个 运行 没有完成任何工作,第二个完成了“一个单元”的工作。
这是有道理的;在第一个中,您在真正的工作开始之前取消了,在第二个中,您在完成 1 个工作单元后但在第二个单元之前取消了。
我已经创建了一个 ASP.NET 核心 API 但我不知道如何正确地实现 IHostedService。 我有几个“工人”类需要运行作为后台进程,所以我使用 IHostedService 异步启动所有任务。
Startup.cs:
services.AddHostedService<BackgroundService>();
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 方法中正确取消所有 运行ning 任务并等待它们完成?
(上面的所有代码都是简化的,它实际上包含了 di 和错误处理,但那是不相关的)
您已请求取消,但您没有等待足够长的时间让任务对其做出反应。我会指定一个超时时间,这样拒绝取消的工作人员就不会停止进程。
cts.Cancel();
Task.WaitAll(workersToRun.ToArray(), TimeSpan.FromSeconds(30));
您的代码有几个问题。
- 您没有将取消令牌传递给
Task.Delay()
,您的工作人员很可能会在这种方法中停留很长一段时间。 (另外,我知道您可能会延迟,以便您可以测试取消,但编码会干扰您的测试)。 cts.Token
给出的令牌与BackgroundService
的cancellationToken
参数不同(很有可能)。- 您正在
StartAsync
中执行Task.WhenAll()
,当您对其应用await
时,它将阻止启动,直到所有任务实际完成。我不认为那是你想要的。 - 取消是合作的,因此您需要在延迟后立即检查取消。当您“真正”删除延迟时,请务必在将要实现的任何内容中添加取消检查
//Do some random async stuff in the background
,包括将令牌传递到您的异步堆栈中。 - 最后,我建议async/await“一路走下去”。
下面是一些消除上述问题的代码。
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");
}
}
最后 driver:
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 毫秒后的结果(通过使用 [=14 提供的令牌调用 StopAsync
=]).
您会注意到第一个 运行 没有完成任何工作,第二个完成了“一个单元”的工作。
这是有道理的;在第一个中,您在真正的工作开始之前取消了,在第二个中,您在完成 1 个工作单元后但在第二个单元之前取消了。