考虑一个 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
在 continuation 中使用与在原始任务中相同的方法。
我们想知道以下事件序列是否可能:
- 用户单击“生成结果”按钮。初始任务 t1 开始。
- 用户再次单击“生成结果”按钮。Windows 消息已发布到队列,但尚未执行处理程序。
- 任务 t1 完成。
- TPL
开始准备开始继续(因为CancellationToken
尚未取消)。任务调度程序将工作发布到 Windows 消息队列(使其在主线程上运行)。 - 第二次单击的 generateResultsButton_Click 开始执行并被
CancellationTokenSource
取消。 - 延续工作开始,它就像令牌没有被取消一样运行(即它在 UI 中显示其结果)。
所以,我认为问题归结为:
当工作发布到主线程时(通过使用TaskScheduler.FromCurrentSynchronizationContext()
),TPLCancellationToken
在执行任务的操作之前检查主线程上的SynchronizationContext
?