考虑一个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为第2点击开始执行和
CancellationTokenSource
被取消。 - 在延续工作开始了它的运作就好像令牌没有取消(即它显示在UI的结果)。
所以,我认为这个问题可以归结为:
当工作发布到主线程(通过使用TaskScheduler.FromCurrentSynchronizationContext()
并在TPL检查CancellationToken
主线程上执行任务的行动之前,或者它检查什么的线程恰好是在取消标记,并然后发布工作到SynchronizationContext
?