【发布时间】:2013-03-27 14:08:10
【问题描述】:
考虑一个 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。
我们想知道以下事件序列是否可能:
- 用户单击“生成结果”按钮。初始任务 t1 已启动。
- 用户再次单击“生成结果”按钮。 Windows 消息已发布到队列,但处理程序尚未执行。
- 任务 t1 完成。
- TPL
开始准备开始继续(因为CancellationToken尚未取消)。任务调度程序将工作发布到 Windows 消息队列(使其在主线程上运行)。 - 第二次点击的 generateResultsButton_Click 开始执行,
CancellationTokenSource被取消。 - 延续工作开始,它就像令牌没有被取消一样运行(即它在 UI 中显示其结果)。
所以,我认为问题归结为:
当工作发布到主线程时(通过使用TaskScheduler.FromCurrentSynchronizationContext()),TPL 会在执行任务操作之前检查主线程上的CancellationToken,还是检查它碰巧在的任何线程上的取消标记上,然后将作品发布到SynchronizationContext?
【问题讨论】:
-
我不确定你在说什么“竞争条件”。但是,不,您不需要额外的取消检查,因为您使用了
TaskContinuationOptions.OnlyOnRanToCompletion选项。 -
即使在任务完成但用户已取消(通过重新单击按钮)的情况下,我们也希望取消更新 UI。我更新了代码以包括清除 UI 标签以更好地演示问题。一旦用户单击按钮,我们希望标签为空,直到显示该单击的结果。
-
一旦延续开始,你就在 UI 线程上(由于
TaskScheduler.FromCurrentSynchronizationContext)。在继续完成之前,UI 线程不能做任何其他事情(一个线程,一次做一件事)。因此,在 4/5/6 的情况下,在延续完成之前,不会出现 5(即不是 5,而是真正的 6) -
对,我明白了,我可能没有用适当的术语解释它,但是当任务 t1 完成时,TPL 具有控制权并“准备”开始继续(这就是我的意思第四步)。这个准备工作大概不是在主线程上完成的。据推测,它在准备中所做的一件事是在开始继续操作之前检查取消令牌。问题是:这些准备工作是否都发生在后台线程上(然后实际工作会发布到主线程),还是准备工作也会发布到主线程?
-
这种类型的工作不会在 SynchronizationContext 线程(即您的情况下的 UI 线程)上完成。点击处理和继续不能同时发生。您可以在继续之前或之后获得点击。如果它在之前,您将总是 在延续中获得取消 - 无论之前的 where (任务正在运行或为延续做准备)。不清楚您为什么认为这可能是个问题?
标签: c# task-parallel-library cancellationtokensource