了解BackgroundService和CancellationToken

huangapple go评论82阅读模式
英文:

Understanding BackgroundService and CancellationToken

问题

I'm quite new to this. There is a lot of information around, and I'm a bit confused. I would like to verify that I understood correctly and appreciate any help. I will number each of my questions in (i) so it would be easier to answer.

我对这个还很陌生。有很多信息,我有点困惑。我想确认我是否理解正确,感谢任何帮助。我会为每个问题编号(i),这样回答会更容易。

I have a BackgroundService with ExecuteAsync(CancellationToken cancellationToken) and StopAsync(CancellationToken cancellationToken).

我有一个BackgroundService,其中包括ExecuteAsync(CancellationToken cancellationToken)和StopAsync(CancellationToken cancellationToken)。

I assume both methods will get the same token when the service is run. (1) Is this true?

我假设在运行服务时,这两个方法都会获得相同的令牌。(1) 这是真的吗?

If I understand correctly, I'm supposed to use the cancellationToken in ExecuteAsync to do something as long as cancellationToken is not marked for cancellation. ExecuteAsync has a while loop that runs iterations while cancellationToken is not cancelled. Each iteration awaits something to happen and may launch some async Tasks.

如果我理解正确,我应该在ExecuteAsync中使用cancellationToken,在cancellationToken未标记为取消时执行某些操作。ExecuteAsync有一个循环,在cancellationToken未取消的情况下运行迭代。每个迭代等待某些事情发生,并可能启动一些异步任务。

When I launch these tasks, I should give them a CancellationToken from a new CancellationTokenSource that is linked to cancellationToken so that (A) the task knows the stop both when the Service should be cancelled and when the task itself should be cancelled (but, in the latter case, without cancelling the Service itself); and (B) so that cancellationToken exhausted. (2) Are (A) and (B) correct?

当我启动这些任务时,我应该为它们提供一个来自新的CancellationTokenSource的CancellationToken,该CancellationTokenSource与cancellationToken关联,以便(A)任务知道何时停止,无论服务是否应该取消以及任务本身是否应该取消(但在后一种情况下,不会取消服务本身);和(B)以便cancellationToken耗尽。(2) 这是正确的吗?

Simply calling Cancel() on some CancellationTokenSource does not actually automatically cancels my Tasks or throws an exception. It is up to me to define this logic. (3) Is this true?

仅仅调用某个CancellationTokenSource上的Cancel()方法并不会自动取消我的任务或引发异常。由我来定义这个逻辑。(3) 这是真的吗?

Which bring me to another question. Say that I discover insider some task that it was indeed marked for cancellation. What is the correct/expected behavior? Should I clean up the Task's resources and then use ThrowIfCancellationRequested() on the token that I have? (4) Is this true?

这就引出了另一个问题。假设我在某个任务内部发现它确实已被标记为取消。正确/预期的行为是什么?我应该清理任务的资源,然后在我拥有的令牌上使用ThrowIfCancellationRequested()吗?(4) 这是真的吗?

Lastly, how should I behave when the service itself is cancelled, i.e., when the main loop of ExecuteAsync discovers that its token is cancelled. (5) Should I immediately ThrowIfCancellationRequested? Should I first clean up resources and only then ThrowIfCancellationRequested? Or should I only call ThrowIfCancellationRequested as the last line of StopAsync?

最后,当服务本身被取消,即当ExecuteAsync的主循环发现其令牌已取消时,我应该如何行为?(5) 我应该立即调用ThrowIfCancellationRequested吗?我应该首先清理资源,然后才调用ThrowIfCancellationRequested吗?还是应该只在StopAsync的最后一行调用ThrowIfCancellationRequested?

英文:

I'm quite new to this. There is a lot of information around, and I'm a bit confused. I would like to verify that I understood correctly and appreciate any help. I will number each of my questions in (i) so it would be easier to answer.

I have a BackgroundService with ExecuteAsync(CancellationToken cancellationToken) and StopAsync(CancellationToken cancellationToken).

I assume both methods will get the same token when the service is run. (1) Is this true?

If I understand correctly, I'm supposed to use the cancellationToken in ExecuteAsync to do something as long as cancellationToken is not marked for cancellation.
ExecuteAsync has a while loop that runs iterations while cancellationToken is not cancelled. Each iteration awaits something to happen and may launch some async Tasks.

When I launch these tasks, I should give them a CancellationToken from a new CancellationTokenSource that is linked to cancellationToken so that (A) the task knows the stop both when the Service should be cancelled and when the task itself should be cancelled (but, in the latter case, without cancelling the Service itself); and (B) so that cancellationToken exhausted. (2) Are (A) and (B) correct?

Simply calling Cancel() on some CancellationTokenSource does not actually automatically cancels my Tasks or throws an exception. It is up to me to define this logic. (3) Is this true?

Which bring me to another question. Say that I discover insider some task that it was indeed marked for cancellation. What is the correct/expected behavior? Should I clean up the Task's resources and then use ThrowIfCancellationRequested() on the token that I have? (4) Is this true?

Lastly, how should I behave when the service itself is cancelled, i.e., when the main loop of ExecuteAsync discovers that its token is cancelled. (5) Should I immediately ThrowIfCancellationRequested? Should I first clean up resources and only then ThrowIfCancellationRequested? Or should I only call ThrowIfCancellationRequested as the last line of StopAsync?

答案1

得分: 2

> 我有一个BackgroundService,其中有ExecuteAsync(CancellationToken cancellationToken)和StopAsync(CancellationToken cancellationToken)。

不要。你在这里混合了不同层次的抽象。要么使用BackgroundServiceExecuteAsync,要么使用IHostedServiceStartAsync以及StopAsync。你可能只需要ExecuteAsync。对于大多数人来说,这是可以的。

> 我假设当服务运行时,这两个方法都会获得相同的令牌。 (1) 这是真的吗?

不是的。传递给StartAsyncStopAsyncExecuteAsync的取消令牌都是不同的。

> 如果我理解正确,我应该在ExecuteAsync中使用cancellationToken,只要cancellationToken没有标记为取消就要执行某些操作。

是的。传递给ExecuteAsync的取消令牌在后台服务停止时被取消。

> 当我启动这些任务时,我应该给它们一个与cancellationToken关联的新CancellationTokenSource的CancellationToken吗?

你是否需要仅取消单个任务?如果是的话,那么是的。如果不是,直接传递cancellationToken即可。

> 仅仅调用某个CancellationTokenSource上的Cancel()并不会自动取消我的任务或引发异常。这取决于我来定义这个逻辑。 (3) 这是真的吗?

是的。取消令牌必须被观察。最常见的做法是:

  • 将它们传递给任何接受它们的方法。99%的情况下,这就是你需要做的。
  • (主要用于CPU绑定的任务)定期调用ThrowIfCancellationRequested
  • (主要用于I/O绑定的任务)注册回调以执行实际的取消操作。

> 这让我想到了另一个问题。假设我在某个任务内部发现它确实被标记为取消了。正确/预期的行为是什么?我应该清理任务的资源,然后在我手头的令牌上使用ThrowIfCancellationRequested()吗? (4) 这是真的吗?

更好的做法是:始终在using语句中定义资源。然后,如果需要,你可以直接调用ThrowIfCancellationRequested

再次强调,99%的情况下,正确处理取消只是将令牌传递给已经支持取消的其他方法。

> 最后,当服务本身被取消时,也就是当ExecuteAsync的主循环发现它的令牌被取消时,我应该如何行事。 (5) 我应该立即调用ThrowIfCancellationRequested吗?我应该首先清理资源,然后才调用ThrowIfCancellationRequested?

以完全相同的方式处理它:如果你的方法可以被取消,就将取消令牌传递下去。将资源放在using块中。如果你需要检查(大多数情况下不需要),你可以定期调用ThrowIfCancellationRequested

具体来说:

> 每次迭代都等待某些事件发生,可能会启动一些异步任务。

如果你的“等待某些事件发生”是可取消的,而你的“启动一些异步任务”也是可取消的,那么只需将取消令牌传递给这两个方法,无需轮询。

> 或者我只应该在StopAsync的最后一行调用ThrowIfCancellationRequested?

如果你使用ExecuteAsync,不要使用StopAsync

英文:

> I have a BackgroundService with ExecuteAsync(CancellationToken cancellationToken) and StopAsync(CancellationToken cancellationToken).

Don't. You're mixing different levels of abstractions here.

Either use BackgroundService with ExecuteAsync or use IHostedService with StartAsync and StopAsync.

You probably just need ExecuteAsync. That's fine for most people.

> I assume both methods will get the same token when the service is run. (1) Is this true?

No. The cancellation tokens passed to StartAsync, StopAsync and ExecuteAsync are all different.

> If I understand correctly, I'm supposed to use the cancellationToken in ExecuteAsync to do something as long as cancellationToken is not marked for cancellation.

Yes. The cancellation token passed to ExecuteAsync is cancelled when it's time for the background service to stop.

> When I launch these tasks, I should give them a CancellationToken from a new CancellationTokenSource that is linked to cancellationToken

Do you ever need to cancel just a single task? If so, then yes. If not, then just pass the cancellationToken on directly.

> Simply calling Cancel() on some CancellationTokenSource does not actually automatically cancels my Tasks or throws an exception. It is up to me to define this logic. (3) Is this true?

Yes. Cancellation tokens must be observed. This is most commonly done by:

  • Passing them down to any methods that take them. 99% of the time this is all you need to do.
  • (Mainly for CPU-bound tasks) Periodically calling ThrowIfCancellationRequested.
  • (Mainly for I/O-bound tasks) Registering a callback to perform the actual cancellation.

> Which bring me to another question. Say that I discover insider some task that it was indeed marked for cancellation. What is the correct/expected behavior? Should I clean up the Task's resources and then use ThrowIfCancellationRequested() on the token that I have? (4) Is this true?

Better: always have your resources defined in using statements. Then you can just ThrowIfCancellationRequested if you need to.

Again, 99% of the time, properly handling cancellation is just a matter of passing the token on down to some other method that already supports cancellation.

> Lastly, how should I behave when the service itself is cancelled, i.e., when the main loop of ExecuteAsync discovers that its token is cancelled. (5) Should I immediately ThrowIfCancellationRequested? Should I first clean up resources and only then ThrowIfCancellationRequested?

Handle it the same exact way: if your methods are cancellable, just pass it on down. Keep resources in using blocks. If you need to check (most don't), you can periodically call ThrowIfCancellationRequested.

Specifically:

> Each iteration awaits something to happen and may launch some async Tasks.

If your "awaits something to happen" is cancellable and your "launch some async Tasks" is cancellable, then just pass the cancellation token to both of these methods. No polling is necessary.

> Or should I only call ThrowIfCancellationRequested as the last line of StopAsync?

Don't use StopAsync if you're using ExecuteAsync.

huangapple
  • 本文由 发表于 2023年6月29日 22:33:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76582071.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定