考虑一个Winforms应用程序,我们有一个生成一些结果的按钮。如果用户第二次按下该按钮,则应取消第一个生成结果的请求并开始新的结果。
我们使用以下模式,但我们不确定是否有必要使用某些代码来防止竞争条件(请参阅注释掉的行)。
private CancellationTokenSource m_cts;
private void generateResultsButton_Click(object sender, EventArgs e)
{
// Cancel the current generation of results if necessary
if (m_cts != null)
m_cts.Cancel();
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
// **Edit** Clearing out the label
m_label.Text = String.Empty;
// **Edit**
Task<int> task = Task.Run(() =>
{
// Code here to generate results.
return 0;
}, ct);
task.ContinueWith(t =>
{
// Is this code necessary to prevent a race condition?
// if (ct.IsCancellationRequested)
// return;
int result = t.Result;
m_label.Text = result.ToString();
}, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
}
注意:
CancellationTokenSource
。CancellationToken
。我们想知道以下事件序列是否可能:
CancellationToken
尚未取消)。任务计划程序将工作发布到Windows消息队列(以使其在主线程上运行)。CancellationTokenSource
被取消。所以,我认为这个问题归结为:
当工作发布到主线程(通过使用TaskScheduler.FromCurrentSynchronizationContext()
)时,TPL会在执行任务的操作之前检查主线程上的CancellationToken
,还是在任何线程上检查取消令牌它恰好打开,然后将工作发布到SynchronizationContext
?
答案 0 :(得分:5)
假设我正确地阅读了这个问题,您会担心以下一系列事件:
T0
,继续C0
被安排为T0
的延续,以便在同步上下文的任务调度程序上运行T0
完成后,会导致C0
发布到邮件队列中。该队列现在包含两个项目,即单击处理程序和C0
的执行。T0
和C0
的令牌信号。然后它以与步骤T1
相同的方式在线程池和C1
上安排1
作为延续。C0
”消息仍在队列中,因此立即处理。它是否执行了您打算取消的延续?答案是否定的。 TryExecuteTask将不会执行已发出取消信号的任务。该文档暗示了这一点,但明确在TaskStatus页面上明确说明了
已取消 - 当令牌处于信号状态时,任务通过抛出具有自己的CancellationToken的OperationCanceledException来确认取消,或任务的CancellationToken在任务开始执行之前已经发出信号强>
所以在一天结束时T0
将处于RanToCompletion
状态,C0
将处于Canceled
状态。
当然,这就是假设当前SynchronizationContext
不允许同时运行任务(正如您所知,Windows窗体不会 - 我只是注意到这不是同步上下文的要求)
此外,值得注意的是,关于是否在请求取消或执行任务的情况下检查取消令牌的最终问题的确切答案,答案实际上是两者 。除了TryExecuteTask
的最终检查之外,一旦请求取消,框架将调用TryDequeue
,这是任务调度程序可以支持的可选操作。同步上下文调度程序不支持它。但如果它以某种方式做到了,差异可能是“执行C0
”消息将完全从线程的消息队列中删除,甚至不会尝试执行任务。
答案 1 :(得分:-1)
我看到它的方式,无论哪个线程检查CencellationToken,您都必须考虑继续进行调度的可能性,并且用户可以在执行延续时取消请求。因此,我认为应该检查已注释掉的检查,并且应该在阅读结果后检查AGAIN:
task.ContinueWith(t =>
{
// Is this code necessary to prevent a race condition?
if (ct.IsCancellationRequested)
return;
int result = t.Result;
if (ct.IsCancellationRequested)
return;
m_label.Text = result.ToString();
}, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
我还会添加一个连续分别处理取消条件:
task.ContinueWith(t =>
{
// Do whatever is appropriate here.
}, ct, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
通过这种方式,您可以涵盖所有可能性。