C# 只需要';最近的';任务-取消/忽略的最佳实践?

C# 只需要';最近的';任务-取消/忽略的最佳实践?,c#,multithreading,c#-4.0,task,C#,Multithreading,C# 4.0,Task,我的任务如下所示: var task = Task.Factory.StartNew <object>(LongMethod); task.ContinueWith(TaskCallback, TaskScheduler.FromCurrentSynchronizationContext()); var task=task.Factory.StartNew(LongMethod); task.ContinueWith(TaskCallback,TaskScheduler.FromC

我的任务如下所示:

var task = Task.Factory.StartNew <object>(LongMethod);
task.ContinueWith(TaskCallback, TaskScheduler.FromCurrentSynchronizationContext());
var task=task.Factory.StartNew(LongMethod);
task.ContinueWith(TaskCallback,TaskScheduler.FromCurrentSynchronizationContext());
LongMethod调用一个长时间运行的服务,在此期间,我不能(或者至少,我认为我不能)不断轮询取消令牌以查看它是否已被取消。但是,我对“取消”或忽略回调方法感兴趣

调用TaskCallback时,我只对来自最近任务的“结果”感兴趣(假设LongMethod调用的服务保持顺序,并且还假设用户可以多次单击按钮,但只有最新的一个相关)

我以以下方式修改了代码:创建任务后,将其添加到堆栈顶部。在TaskCallback中,我检查传递给回调的任务是否是最近的任务(即堆栈顶部的TryPeek)。如果不是,我就忽略结果

private ConcurrentStack<Task> _stack = new ConcurrentStack<Task>();

private void OnClick(object sender, ItemClickEventArgs e)
{
    var task = Task.Factory.StartNew < object >( LongMethod);
    task.ContinueWith(TaskCallback, TaskScheduler.FromCurrentSynchronizationContext());
    _stack.Push(task);
 }


private void TaskCallback(Task<object> task)
{
    Task topOfStack;
    if(_stack.TryPeek(out topOfStack)) //not the most recent
    {
        if (task != topOfStack) return;
    }
    //else update UI
}
private ConcurrentStack _stack=new ConcurrentStack();
private void OnClick(对象发送方,ItemClickEventArgs e)
{
var task=task.Factory.StartNew(LongMethod);
task.ContinueWith(TaskCallback,TaskScheduler.FromCurrentSynchronizationContext());
_stack.Push(任务);
}
私有void任务回调(任务)
{
任务拓扑学;
if(_stack.TryPeek(out topOfStack))//不是最新的
{
if(task!=topOfStack)返回;
}
//其他更新用户界面
}


我很确定这不是一个“最佳实践”解决方案。但这是什么呢?传递和维护取消代币看起来也不那么优雅

我个人认为以下方法是最优雅的:

// Cancellation token for the latest task.
private CancellationTokenSource cancellationTokenSource;

private void OnClick(object sender, ItemClickEventArgs e)
{
    // If a cancellation token already exists (for a previous task),
    // cancel it.
    if (this.cancellationTokenSource != null)
        this.cancellationTokenSource.Cancel();

    // Create a new cancellation token for the new task.
    this.cancellationTokenSource = new CancellationTokenSource();
    CancellationToken cancellationToken = this.cancellationTokenSource.Token;

    // Start the new task.
    var task = Task.Factory.StartNew<object>(LongMethod, cancellationToken);

    // Set the task continuation to execute on UI thread,
    // but only if the associated cancellation token
    // has not been cancelled.
    task.ContinueWith(TaskCallback, 
        cancellationToken, 
        TaskContinuationOptions.NotOnCanceled, 
        TaskScheduler.FromCurrentSynchronizationContext());
}

private void TaskCallback(Task<object> task)
{
    // Just update UI
}
//最新任务的取消令牌。
私有CancellationTokenSource CancellationTokenSource;
private void OnClick(对象发送方,ItemClickEventArgs e)
{
//如果取消令牌已经存在(对于上一个任务),
//取消它。
if(this.cancellationTokenSource!=null)
this.cancellationTokenSource.Cancel();
//为新任务创建新的取消令牌。
this.cancellationTokenSource=新的cancellationTokenSource();
CancellationToken CancellationToken=this.cancellationTokenSource.Token;
//开始新任务。
var task=task.Factory.StartNew(LongMethod,cancellationToken);
//将任务继续设置为在UI线程上执行,
//但仅当关联的取消令牌
//没有取消。
task.ContinueWith(TaskCallback,
取消令牌,
TaskContinuationOptions.notinconceled,
TaskScheduler.FromCurrentSynchronizationContext());
}
私有void任务回调(任务)
{
//只需更新用户界面
}

我知道您不能取消长时间运行的任务,但希望在用户取消任务时,从用户的角度立即中止该过程

与长时间运行的任务同时启动“取消”任务。继续关闭
任务。当有(长运行任务,取消任务)
时,检查完成的任务是长运行任务还是取消任务。然后,您可以决定要做什么(显示结果或更新UI)


当您取消“取消任务”时,继续操作将立即启动。

我认为您应该将
CancellationToken
也传递到
StartNew()
。这样,
任务就可以在开始之前取消。@svick:在什么情况下才可能呢?根据上述逻辑,
CancellationToken
只能从UI线程中取消,而只要调用
StartNew
方法,新任务就可以在后台线程上异步执行。如果调用
StartNew()
,并不意味着
任务实际上立即启动,它只是在
线程池中安排的。如果池忙,可能需要一段时间才能启动。在这种情况下,
任务
可能会在开始运行之前被取消,如果您给它
CancellationToken
@svick:看起来您是对的(尽管在内部检查
CancellationToken
时,我仍然找不到任何源代码澄清)。我正在根据您的建议编辑代码。这比道格拉斯建议的要好多少:将
取消令牌
传递到
ContinueWith()
?您可以连接一个继续,以防用户取消。这与仅作为cancel click处理程序的一部分执行代码不同:长时间运行的任务完成后,cancel click可能刚好到达,从而导致成功延续和cancel代码之间的竞争。