如何取消异步任务?

How to cancel an asynchronous Task?

我有一个加载数据的方法,例如:

public async GetCustomers()
{
    await Task.Run(() => {
        for (int i = 0; i < 99; i++)
            customers = customerRequest.GetCustomers();
    });
}

customerRequest 是一个简单的 class,它使用 HttpClient 连接到 WebApi 服务器。调用栈如下:

method->request->controller action->server layer->repository->db

目前,控制器操作 returns IHttpActionResult,而服务和存储层 return IEnumerable<Customer>。同步调用所有方法。

出于测试目的,我添加了 for 循环以增加任务的延迟并查看正在执行的 SQL 语句。

如果用户决定关闭表单,任务仍在后台执行,SQL 语句仍会发送到数据库。

取消此类任务的正确方法是什么?

签出 CancellationTokenSource。您可以保留其中一个长期存在的按钮或关闭事件调用 Cancel()。如果您希望所有任务都取消,则只需要将其传递给所有任务即可。该任务将抛出异常,因此请确保包装在 try catch 中。您也可以查看您的任务中是否有取消请求,尝试优雅地闯关。

var cts = new CancellationTokenSource();

await Task.Run(() =>
{
    //do work, will throw if cts.Cancel() is called
}, cts.Token);

//wait 2 seconds then cancel
await Task.Delay(2000);

cts.Cancel();

您想使用 CancellationTokenSource,并将其 CancellationToken 传递到您的方法中。当表单关闭时,调用 CancellationTokenSource.Cancel.

但是请注意,您希望将 CancellationToken 传递给实际使用它的方法。正如我在我的博客中所描述的那样,Task.Run is not one of them.

你真正想要做的是从最低级别(在客户端)开始,然后将 CancellationToken 传递给你正在使用的任何 HttpClient 方法(即 like this one).

然后继续努力。我还建议让您的代码异步。完成后,您应该得到如下所示的 GetCustomers

public async Task GetCustomersAsync(CancellationToken token)
{
  for (int i = 0; i < 99; i++)
    customers = await customerRequest.GetCustomersAsync(token);
}

如果您想真正确定 (tm) 没有虚假请求发出,您还可以在执行请求之前明确检查令牌:

public async Task GetCustomersAsync(CancellationToken token)
{
  for (int i = 0; i < 99; i++)
  {
    token.ThrowIfCancellationRequested();
    customers = await customerRequest.GetCustomersAsync(token);
  }
}

如果这对你很重要,你也可以handle cancellation on the server side