【问题标题】:CancellationToken in Web API not working correctlyWeb API 中的 CancellationToken 无法正常工作
【发布时间】:2021-04-15 10:52:22
【问题描述】:

我有从用户那里获取 CancellationToken 的 Web Api,其中的方法(DoWork)也获取 CancellationToken:

[HttpPost]
public async Task<long> GetInfo(CancellationToken cancellationToken)
{
   long result = 0;
   bool notDone = true;
   Task<long> t = Task.Run(async () =>
   {

      if (cancellationToken.IsCancellationRequested)
         cancellationToken.ThrowIfCancellationRequested();

      while (notDone && !cancellationToken.IsCancellationRequested)
      {
         result = await DoWork(cancellationToken);
         notDone = false;
      }

      return result;
   }, cancellationToken);
   try
   {
      return await t;
   }
   catch (AggregateException e)
   {
      Debug.WriteLine("Exception messages:");
      foreach (var ie in e.InnerExceptions)
         Debug.WriteLine("   {0}: {1}", ie.GetType().Name, ie.Message);

      Debug.WriteLine("\nTask status: {0}", t.Status);
      throw;
   }
   catch (Exception ex)
   {
      throw;
   }
}

private Task<long> DoWork(CancellationToken token)
{
   long result = 0;
   bool notDone = true;

   Task<long> task = Task.Run(() =>
   {

      if (token.IsCancellationRequested)
         token.ThrowIfCancellationRequested();

      while (notDone && !token.IsCancellationRequested)
      {
         Thread.Sleep(8000);
         result = 2;
         notDone = false;
      }

      return result;
   }, token);

   return task;
}

我希望当用户取消请求时,它会中止 DoWork 方法并且不继续该功能,但是在发送异常之后,当“Thread.Sleep”完成时,DoWork 方法会继续。 用户可以像这种方法“cc”一样调用 API 服务,您可以看到它在 5 秒后取消,而在 DoWork 方法中“Thread.Sleep”是 9 秒。用户收到异常,但方法仍在运行。

private async Task<bool> cc()
{
   UriBuilder builder = new UriBuilder("http://localhost:12458/api/Test/GetInfo");

   ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
   HttpClient client = new HttpClient();
   System.Threading.CancellationTokenSource s = new System.Threading.CancellationTokenSource();
   s.CancelAfter(5000);
   try
   {
      var result = client.PostAsJsonAsync<model1>(builder.ToString(), new model1 { }, s.Token).Result;
      string tmp = result.Content.ReadAsStringAsync().Result;
      long ApiResult = JsonConvert.DeserializeObject<long>(tmp);
   }
   catch (TaskCanceledException ex)
   {
   }
   catch (OperationCanceledException ex)
   {
   }
   catch (Exception ex)
   {
   }
   finally
   {
      s.Dispose();
   }
   return false;
}

【问题讨论】:

  • 这段代码是一团糟,它很难帮助,因为有很多可疑的东西正在发生。我的意思是,notDone 有什么意义。这个循环应该在一次迭代后完成,所以我猜它不是你真正的代码。此外,您正在运行假异步(请参阅 stephen cleary)。最后,您试图捕获 AggregateException 的事实表明您还有其他可疑代码正在运行,您没有显示。
  • 让我们退后一步。你想达到什么目的?如果我们知道我们可能能够提供一个简单的工作解决方案。
  • 亲爱的@00110001 我希望当用户取消请求时,Web API 服务和其中的方法同时取消,在我的代码“DoWork”中,当用户取消请求时,方法在 Thread.Sleep(8000); 之后继续;
  • 好的,如果您无法控制该代码,则没有可靠的方法可以轻松地阻止它。从这里你有 2 个选项,你可以放弃它......这意味着代码仍然会运行,如果它有副作用,那么你不能做太多事情。第二种方法是将此代码加载到一个新的应用程序域中并强制将其删除(这不是最佳的,但它可以比尝试中止当前域中的线程更安全)。
  • 不,你不应该在你当前的域中使用Thread.Abort,唯一相对安全的使用它的方法是加载一个新的AppDomain,在那里运行线程,然后在它没有的地方中止破坏某些东西的机会

标签: c# asp.net-web-api async-await task cancellation-token


【解决方案1】:

好的,在您的代码中,您也应该处理 CancellationToken.IsCancellationRequested。这不是什么魔法,你应该做这项工作。

    public void DoWork(CancellationToken ctsToken)
    {
        ctsToken.ThrowIfCancellationRequested();
        DoSomething();

        ctsToken.ThrowIfCancellationRequested();
        DoSomethingElse();

        // end so on with checking CancellationToken before every part of work
    }

你的任务应该是这样的

    Task<long> t = Task.Run(async () =>
    {
        cancellationToken.ThrowIfCancellationRequested();

        result = await DoWork(cancellationToken);
        notDone = false;

        cancellationToken.ThrowIfCancellationRequested();

        return result;
    }, cancellationToken);

【讨论】:

    【解决方案2】:

    当使用Thread.Sleep(8000) 时实际持有主线程8 秒,它无法检查取消令牌。您应该像这样使用带有取消令牌的 Task.Delay:

    while (notDone && !token.IsCancellationRequested)
      {
         await Task.Delay(8000, token);
         result = 2;
         notDone = false;
      }
    

    Task.Delay 检查取消令牌本身。

    【讨论】:

    • Task.Delayawaited。否则,它会变成一个对程序零影响的即发即弃任务(除了为垃圾收集器添加工作)。
    • Thread.Sleep(8000) 不是我真正的代码,我只是把它放在这里显示我的代码需要多长时间才能完全执行。
    • @TheodorZoulias 是的,你是对的。我编辑了答案
    • @Lana 你应该很少(如果有的话)检查IsCancellationRequested 属性。调用一致取消的正确 API 是 ThrowIfCancellationRequested 方法。 .NET 中的标准取消模式通过抛出和处理 OperationCanceledException 来传达取消。
    • @TheodorZoulias 你是绝对正确的我已经改变了我的答案。
    猜你喜欢
    • 2021-03-24
    • 1970-01-01
    • 2013-07-02
    • 1970-01-01
    • 1970-01-01
    • 2013-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多