从TaskScheduler中取消TPL任务

时间:2015-02-08 03:30:10

标签: c# .net task-parallel-library

我试图创建一个按顺序运行所有任务的TaskScheduler,但只会完成'最近安排的任务。例如,如果我使用它来安排任务A,那么在它完成任务B和C的计划之前,我只希望C被认为是成功的。 A可以继续工作,但应该考虑取消'在完成时,B应该在它开始之前被标记为取消。

我已经获得了在线程池上顺序执行委托的现有代码,并且管理了最多有2个排队任务的想法 - 一个当前正在执行,另一个是下一个。缺少的部分是能够将任务的结果状态设置为已取消

不幸的是,从TaskScheduler开始,您实际上很少能够访问Task或任何CancellationToken的状态。

我试图通过跟踪最后排队的任务来解决这个问题,并且在执行任务时,如果TaskCancelledException不等于最后排队的任务,则抛出TryExecuteTask(),但这似乎不起作用。我想这是因为任务的代表内部并没有抛出异常,而且所有的魔法都被排除在外。实际上是在public class CurrentPendingTaskScheduler : TaskScheduler { private readonly ThreadSafeCurrentPendingQueueProcessor<Task> _Processor; private Task _LastTask; public CurrentPendingTaskScheduler() { _Processor = new ThreadSafeCurrentPendingQueueProcessor<Task>(); _Processor.Process += _Processor_Process; } private void _Processor_Process(Task obj) { // If there's a newer task already, cancel this one before starting if (obj != _LastTask) throw new TaskCanceledException(obj); TryExecuteTask(obj); // If a newer task was added whilst we worked, cancel this one if (obj != _LastTask) throw new TaskCanceledException(obj); } protected override void QueueTask(Task task) { _LastTask = task; _Processor.Enqueue(task); } protected override Boolean TryExecuteTaskInline(Task task, Boolean taskWasPreviouslyQueued) { return false; } protected override IEnumerable<Task> GetScheduledTasks() { throw new NotImplementedException(); } } 内处理的。

以下是我所拥有的:

ThreadSafeCurrentPendingQueueProcessor<>

QueueTask类是一个帮助器,它通过一个事件回调,以便处理单个后台线程上的入队项目,只允许一个活动项目和一个待处理项目。

如果&#39;最后一项任务&#39;在处理器回调之前已经改变,该异常只是阻止任务运行(但不会影响其状态)。如果回调确实可以运行但是最后一个任务&#39;在此期间发生了变化,在任何延续开始之后,异常就已经太晚了。

此外我根本不确定是什么原因,但是第一次使用调度程序(我为每个UI元素点击安排一个任务),_LastTask被调用一次,新任务。然而,对于每个后续调度,它被调用两次。随着TaskCompletionSource<>被覆盖,这会使事情进一步混乱。

我觉得TaskScheduler可能有用,但不太清楚。

是否可以实现按照描述工作的{{1}}?我知道我可以在调度程序之外,在我创建任务的时候实现这种行为,但我需要在很多地方使用它,并试图通过将它放在可重用的调度程序中来使生活更轻松。 / p>

2 个答案:

答案 0 :(得分:0)

我会创建一个辅助类,它接受输入操作,启动它,取消现有的操作,并覆盖其内部变量。由于您无法在Cancel()上明确直接执行Task<T>,因此您需要保留自己的TaskCancellationSource。如果你想提供一个外部令牌,你可以将它们与CancellationTokenSource.CreateLinkedTokenSource(...)结合使用。如果你需要关注结果,这将为TaskCompletionSource提供一个很好的机会。

public class OverwriteTaskHandler<T>
{
    private Task<T> _task;
    private TaskCompletionSource<T> _tcs;
    private CancellationTokenSource _cts;

    public OverwriteTaskHandler(Func<T> operation)
    {
        _tcs = new TaskCompletionSource<T>();
        _cts = new CancellationTokenSource();
        TryPushTask(operation);
    }

    public bool TryPushTask(Func<T> operation)
    {
        if (_tcs.Task.IsCompleted)
            return false;   //It would be unsafe to use this instance as it is already "finished"
        _cts.Cancel();
        _cts = new CancellationTokenSource();
        _task = Task.Run(operation, _cts.Token);
        _task.ContinueWith(task => _tcs.SetResult(task.Result));
        return true;
    }

    public void Cancel()
    {
        _cts.Cancel();
    }

    public Task<T> WrappedTask { get { return _tcs.Task; } }
}

Discalimer:我没有对此进行过测试,所以请仔细检查一下!

答案 1 :(得分:0)

AFAIK TaskScheduler无法用于完成这项工作。你真的需要别的东西。例如,您可以编写一个具有以下用法的帮助程序类:

static CurrentPendingTaskContext ctx = ...;

async Task MyAsyncFunc() {
 await ctx.RegisterAndMaybeCancel();
 try {
  //rest of method
 }
 finally {
  ctx.NotifyCompletion();
 }
}

RegisterAndMaybeCancel将等到当前正在运行的任务完成。如果此特定任务已被另一个任务取代,则会抛出取消异常。

我现在没有时间实施该课程(尽管它很诱人)。但我认为这种语法模式很简单,你可以在很多地方使用它。

您也可以使用IDisposable模式来摆脱finally:

async Task MyAsyncFunc() {
 using (await ctx.RegisterAndMaybeCancel())
 {
      //rest of method
 }
}