没有CancellationToken

时间:2017-06-14 09:05:20

标签: c# async-await cancellation

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

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

    public async static Task HandleCancellation(this Task asyncTask, CancellationToken cancellationToken)
    {
        // Create another task that completes as soon as cancellation is requested. http://stackoverflow.com/a/18672893/1149773
        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。

有没有办法杀死&#34;没有杀死进程的任务?

3 个答案:

答案 0 :(得分:6)

  

我正在使用另一个StackOverflow问题的扩展方法

该代码非常陈旧。

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

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

我喜欢API如何结束,因为更明确的是等待被取消,而不是操作本身。

  

有没有办法在不杀死进程的情况下“杀死”任务?

没有

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

除此之外,您正处于“取消无法解决的操作”场景,可以通过以下方式解决:

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

答案 1 :(得分:0)

如您所知,您可以通过传递CancellationToken然后拨打Cancel取消任务。

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

一些可能的情况

  1. 继续,直到您点击取消
  2. 在固定时间后取消
  3. 如果固定时间没有进展,则取消
  4. 在案例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 programmingtask cancellationprogress and cancellationcancel tasks after set timecancel a list of tasks

答案 2 :(得分:0)

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

基本概念是创建自己的TaskScheduler实现,在分配了自己的调度程序的情况下启动新任务。这样,您就可以使调度程序成为当前调度程序,并从此任务开始执行有问题的任务。

仍然有可能无效的原因。如果导致您麻烦的任务使用默认任务计划程序创建更多任务,您仍然会遇到同样的问题。 (Task.Run does so)

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

现在,在您自己控制的情况下,您可以使用Thread.Abort来终止任何任务。

要了解实施能力,您应该看一下ThreadPoolTaskScheduler。这是调度程序的默认实现。

正如我所说,这是一项很多工作,但我能想到的唯一办法就是杀掉无法取消的任务。

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