【问题标题】:Easy cancellation of a task? [closed]轻松取消任务? [关闭]
【发布时间】:2019-09-04 07:26:06
【问题描述】:

在我的应用程序中,我有一个按钮可以启动一个长时间运行的任务。为了不阻止 GUI,我在后台任务中执行此操作。按下按钮时执行以下函数:

private async void StartButtonHandler()
{
 await Task.Run(() =>
 {
  FirstAction();
  SecondAction();
  ThirdAction();
 });
}

现在我想让这个可以取消。我添加了第二个按钮并像这样修改了代码:

private CancellationTokenSource tokenSource;

private async void StartButtonHandler()
{
 tokenSource?.Dispose();
 tokenSource = new CanncellationTokenSource();
 CancellationToken token = tokenSource.Token;
 await Task.Run(() =>
 {
  FirstAction();
  if(token.IsCancellationRequested)
  {
   return;
  }
  SecondAction();
  if(token.IsCancellationRequested)
  {
   return;
  }
  ThirdAction();
 });
}

private void CancelButtonHandler()
{
 tokenSource.Cancel();
}

这很好用,但是当代码变长时会变得不舒服。想象一下,FirstAction() 函数再次调用了几个长时间运行的函数,我想在 FirstFunction() 中添加一个取消任务的地方。我必须像这样编写函数 FirstAction():

private void FirstAction(CancellationToken token)
{
 FirstSubAction();
 if(token.IsCancellationRequested)
 {
  return;
 }
 SecondSubAction();
 if(token.IsCancellationRequested)
 {
  return;
 }
 ThirdSubAction();
 if(token.IsCancellationRequested)
 {
  return;
 }
 FourthSubAction();
}

所以我的问题是:是否有一些模式可以帮助我使这段代码更清晰?在我看来,所有这些 if 语句都会分散真正发生的事情。

【问题讨论】:

  • 这就是CancellationToken.ThrowIfCancellationRequested() 的用途。您可以从内部方法调用它并在外部级别处理OperationCanceledException
  • 您可以通过token.ThrowIfCancellationRequested(); 尝试抛出异常,这在您想知道任务已完成还是未完成时很有用(在某个阶段被打断
  • 我认为问题在于他想要某种方式将一大块代码包含在一个范围内,如果有人在TaskCancellationSource 上使用Cancel,任务将结束。不幸的是你不能@987654330 @ 来自外部的任务,您只能使用 IsCancellationRequestedThrowIfCancellationRequested 从内部取消它们

标签: c# task cancellation


【解决方案1】:

我会用以下方式重写该方法,请注意,要使其正常工作,所有子操作都应具有相同的方法签名void methodName(CancellationToken token)

private void FirstAction(CancellationToken token)
{
    Action<CancellationToken>[] actions = new Action<CancellationToken>[]{FirstSubAction, SecondSubAction, ThirdSubAction, ForthSubAction};
    foreach(Action<CancellationToken> action in actions)
    {
        if(token.IsCancellationRequested) return;
        action(token);
    }
}

【讨论】:

  • 好答案,我正在寻找这样的东西。我必须检查我是否能够将它应用到我的真实代码中。答案的一个扩展:如果你的函数中有不同类型的参数,你可以使用 lambda 表达式:Action[] actions = new Action[] { () =&gt; FirstSubAction("some string"), () =&gt; SecondSubAction(5), …};
  • 好主意,如果您需要不同的签名,您可以编辑您的问题,然后编辑我的答案或提及我,以便我可以将您的建议添加到我的答案中。目前我正在留下最简单的代码。
  • 我认为这确实使代码更难遵循,与仅在适当的位置使用token.ThrowIfCancellationRequested();(以及OperationCanceledException 的外部处理程序)相比。
  • @MatthewWatson 根据您的建议,您可以将“如果取消则返回”变为“如果取消+尝试/捕获则异常”。不是我个人的偏好,因为不仅代码将具有 OP 试图避免的类似结构,而且还会产生额外的异常成本。无论如何,您可以提出另一个答案,解决问题的不同方法越多,它就越适合类似的用例。
  • 使用IsCancellationRequested 而不是ThrowIfCancellationRequested() 时遇到的一个问题是,无法判断任务是否真的响应了取消 - 尽管很多代码并不关心这一点. (您无法检查令牌的状态,因为在方法返回时可能已将其设置为取消,因此即使令牌被取消,该方法也不会被取消。)与以往一样,您的里程可能会有所不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 2017-11-18
相关资源
最近更新 更多