C# 从TaskScheduler中取消TPL任务
我正在尝试创建一个C# 从TaskScheduler中取消TPL任务,c#,.net,task-parallel-library,C#,.net,Task Parallel Library,我正在尝试创建一个TaskScheduler,它按顺序运行所有任务,但只会“完成”最近计划的任务。例如,如果我使用它来计划任务A,那么在它完成计划任务B和C之前,我只希望C被认为是成功的。A可以继续其工作,但在完成时应被视为“取消”,而B应在开始之前标记为“取消” 我已经有了在线程池上按顺序执行委托的现有代码,并且管理最多2个排队任务的想法——一个当前正在执行,另一个是下一个。缺少的部分能够将任务的结果状态设置为“已取消” 不幸的是,在任务调度器中,您实际上几乎无法访问任务的状态或任何取消令牌
TaskScheduler
,它按顺序运行所有任务,但只会“完成”最近计划的任务。例如,如果我使用它来计划任务A,那么在它完成计划任务B和C之前,我只希望C被认为是成功的。A可以继续其工作,但在完成时应被视为“取消”,而B应在开始之前标记为“取消”
我已经有了在线程池上按顺序执行委托的现有代码,并且管理最多2个排队任务的想法——一个当前正在执行,另一个是下一个。缺少的部分能够将任务的结果状态设置为“已取消”
不幸的是,在任务调度器
中,您实际上几乎无法访问任务
的状态或任何取消令牌
我试图通过跟踪最后一个排队的任务来解决这个问题,并且在执行任务时,如果它不等于最后一个排队的任务,则抛出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();
}
}
公共类CurrentPendingTaskScheduler:TaskScheduler
{
专用只读线程安全CurrentPendingQueueProcessor\u处理器;
私有任务_LastTask;
public CurrentPendingTaskScheduler()
{
_Processor=新的ThreadSafeCurrentPendingQueueProcessor();
_Process+=\u Processor\u进程;
}
私有无效处理程序(任务obj)
{
//如果已经有更新的任务,请在开始之前取消此任务
如果(obj!=\u LastTask)
抛出新的TaskCanceledException(obj);
TryExecuteTask(obj);
//如果在我们工作时添加了更新的任务,请取消此任务
如果(obj!=\u LastTask)
抛出新的TaskCanceledException(obj);
}
受保护的覆盖无效队列任务(任务任务)
{
_LastTask=任务;
_处理器排队(任务);
}
受保护的重写布尔TryExecuteTaskInline(任务任务,布尔任务先前排队)
{
返回false;
}
受保护的重写IEnumerable GetScheduledTasks()
{
抛出新的NotImplementedException();
}
}
ThreadSafeCurrentPendingQueueProcessor
类是一个帮助程序,它通过事件回调以处理单个后台线程上的排队项目,只允许一个活动项目和一个挂起项目
如果“最后一个任务”在处理器回调之前已更改,则异常只会阻止任务运行(但不会影响其状态)。如果回调确实开始运行,但“最后一个任务”在此期间发生了更改,那么在任何延续启动之后,异常抛出的时间就太晚了
我也不确定这是什么原因,但在第一次使用调度程序时(我为每个UI元素单击调度一个任务),QueueTask
会被调用一次,同时调用新任务。但是,对于每个后续的调度,都会调用两次。当\u LastTask
被覆盖时,这会把事情搞得更糟
我觉得TaskCompletionSource
可能有一些用处,但不太清楚如何使用
是否可以实现一个按所述工作的任务调度器
?我知道在创建任务时,我可以在调度程序之外实现这种行为,但我需要在许多地方使用它,并试图通过在可重用的调度程序中使用它来简化工作。我将创建一个助手类,该类接受输入操作,启动它,并取消现有的操作,并覆盖其内部变量。由于您不能直接明确地对任务执行Cancel()
,因此您需要将自己的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; } }
}
公共类OverwriteTaskHandler
{
私人任务(u Task),;
专用任务完成源\u tcs;
私有取消令牌源;
公共OverwriteTaskHandler(Func操作)
{
_tcs=新任务完成源();
_cts=新的CancellationTokenSource();
任务(操作);
}
公共bool任务(Func操作)
{
如果(_tcs.Task.IsCompleted)
return false;//使用此实例是不安全的,因为它已经“完成”
_cts.Cancel();
_cts=新的CancellationTokenSource();
_task=task.Run(操作,_cts.Token);
_task.ContinueWith(task=>\u tcs.SetResult(task.Result));
返回true;
}
公开作废取消()
{
_cts.Cancel();
}
公共任务WrappedTask{get{return}tcs.Task;}
}
Discalimer:我还没有测试过这个,所以请仔细检查一下 AFAIK TaskScheduler无法用于执行此操作。你真的需要别的东西。例如,您可以使用以下用法编写一个帮助器类:
static CurrentPendingTaskContext ctx = ...;
async Task MyAsyncFunc() {
await ctx.RegisterAndMaybeCancel();
try {
//rest of method
}
finally {
ctx.NotifyCompletion();
}
}
RegisterAndMaybeCancel
将等待当前运行的任务完成。如果此特定任务已被另一个任务取代,它将抛出取消异常
我现在没有时间实现这个类(尽管它很诱人)。但我认为这种语法模式非常简单,可以在很多地方使用
你也可以
async Task MyAsyncFunc() {
using (await ctx.RegisterAndMaybeCancel())
{
//rest of method
}
}