在没有 CancellationToken 的情况下停止任务

Stopping a task without a CancellationToken

我正在使用具有 async 方法但没有 CancellationToken 重载的外部库。

现在我正在使用另一个 Whosebug 问题的扩展方法来添加 CancellationToken:

    public async static Task HandleCancellation(this Task asyncTask, CancellationToken cancellationToken)
    {
        // Create another task that completes as soon as cancellation is requested. 
        TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
        cancellationToken.Register(() =>
            tcs.TrySetCanceled(), useSynchronizationContext: false);
        Task cancellationTask = tcs.Task;

        // Create a task that completes when either the async operation completes, or
        // cancellation is requested.
        Task readyTask = await Task.WhenAny(asyncTask, cancellationTask);

        // In case of cancellation, register a continuation to observe any unhandled exceptions
        // from the asynchronous operation (once it completes). In .NET 4.0, unobserved task
        // exceptions would terminate the process.
        if (readyTask == cancellationTask)
            asyncTask.ContinueWith(_ => asyncTask.Exception,
                TaskContinuationOptions.OnlyOnFaulted |
                TaskContinuationOptions.ExecuteSynchronously);

        await readyTask;
    }

但是底层任务仍然执行到完成。这不是什么大问题,但有时底层任务永远不会完成并消耗了我 99% 的 CPU.

有什么方法可以"kill"任务而不终止进程吗?

正如您所建议的,您可以通过传入 CancellationToken 然后调用 Cancel.

来取消任务

至于如何触发取消取决于您的应用程序的性质。

几种可能的情况

  1. 继续,直到您点击取消
  2. 固定时间后取消
  3. 如果在固定时间内没有进展则取消

在情况 1 中,您只需通过取消按钮取消任务,例如

private void cancel_Click(object sender, RoutedEventArgs e)
{
    ...
    cts = new CancellationTokenSource();
    await MyAsyncTask(cts.Token);
    cts.Cancel();
    ...
}

在情况 2 中,您可以在开始任务时启动计时器,然后在设定的时间后使用 CancelAfter 取消任务,例如

private void start_Click(object sender, RoutedEventArgs e)
{
    ...
    cts = new CancellationTokenSource();
    cts.CancelAfter(30000);
    await MyAsyncTask(cts.Token);
    ...
}

在情况 3 中你可以做一些有进展的事情,例如

private void start_Click(object sender, RoutedEventArgs e)
{
    ...
    Progress<int> progressIndicator = new Progress<int>(ReportProgress);
    cts = new CancellationTokenSource();
    await MyAsyncTask(progressIndicator, cts.Token);
    ...
}

void ReportProgress(int value)
{
    // Cancel if no progress
}

这里有一些有用的链接 Parallel programming, task cancellation, progress and cancellation, cancel tasks after set time, and cancel a list of tasks.

我能想到的唯一方法是更改​​ TaskScheduler 并自己管理用于任务的线程的创建。这是很多工作。

基本概念是创建您自己的 TaskScheduler 实现,使用您自己分配的调度程序启动新任务。通过这种方式,您可以让您的调度程序成为当前调度程序,并从此任务开始有问题的任务。

还有一些原因可能不起作用。如果给您带来麻烦的任务使用默认任务计划程序创建了更多任务,您仍然会遇到同样的问题。 (Task.Run does so)

如果他们使用 async/await 关键字,您的调度程序将保持活动状态。

现在有了您自己控制的调度程序,您可以使用 Thread.Abort.

终止任何任务

要了解有关实施的信息,您应该查看 ThreadPoolTaskScheduler。这是调度程序的默认实现。

正如我所说,这是很多工作,但我能想到的唯一方法是终止无法取消的任务。

要获得测试 运行 如果它甚至有效,您可能只想实现 ThreadPoolTaskSchedulerTaskCreationOptions.LongRunning 选项的行为。因此为每个任务生成一个新线程。

I am using an extension method from another Whosebug question

那个代码很旧。

现代 AsyncEx 方法是一种扩展方法 Task.WaitAsync,如下所示:

var ct = new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token;
await myTask.WaitAsync(ct);

我喜欢 API 的结局,因为更清楚的是 wait 被取消,而不是操作本身。

Is there any way to "kill" the task without killing the process?

没有

理想的解决方案是联系您正在使用的库的作者,让他们添加对 CancellationToken.

的支持

除此之外,您处于"cancel an uncancelable operation"场景,可以通过以下方式解决:

  • 将代码放在一个单独的进程中,并在取消时终止该进程。这是唯一完全安全但最困难的解决方案。
  • 将代码放在单独的应用程序域中,并在取消时卸载该应用程序域。这并不完全安全;终止的应用程序域可能导致进程级资源泄漏。
  • 将代码放在一个单独的线程中,并在取消时终止该线程。这更不安全;终止的线程会破坏程序内存。