用的CancellationToken竞争条件,其中CancellationTokenSource只

2019-07-28 11:00发布

考虑一个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在延续,我们在原来做任务。

我们想知道事件将按照下列顺序是否是可能的:

  1. 用户点击“生成结果”按钮。 初始任务T1开始。
  2. 用户点击一次“产生效果”按钮。 的Windows消息发布到队列,但处理尚未执行。
  3. 任务T1完成。
  4. TPL 开始 准备开始延续(因为CancellationToken尚未取消)。 任务调度岗位工作到Windows消息队列(得到它的主线程上运行)。
  5. 该generateResultsButton_Click为第2点击开始执行和CancellationTokenSource被取消。
  6. 在延续工作开始了它的运作就好像令牌没有取消(即它显示在UI的结果)。

所以,我认为这个问题可以归结为:

当工作发布到主线程(通过使用TaskScheduler.FromCurrentSynchronizationContext()并在TPL检查CancellationToken主线程上执行任务的行动之前,或者它检查什么的线程恰好是在取消标记,并然后发布工作到SynchronizationContext

Answer 1:

假设我正确读取的问题,你担心的事件顺序如下:

  1. 点击按钮,任务T0计划线程池,延续C0定为的延续T0 ,对同步上下文的任务调度运行
  2. 该按钮被再次点击。 比方说,消息泵是忙于做其他事情,所以现在的消息队列由一个项目,点击处理程序。
  3. T0完成,这将导致C0被张贴到消息队列。 队列现在包含两个项目,单击处理和执行C0
  4. 单击处理消息被泵送,且该处理程序信号令牌驱动的取消T0C0 。 然后,它调度T1上的线程池和C1作为以相同的方式作为步骤的延续1
  5. 在“执行C0 ”消息是仍在队列中,因此它现在得到处理。 它是否执行您打算取消延续?

答案是不。 TryExecuteTask将不执行已经暗示了取消任务。 它是由该文档暗示,但上明确规定了TaskStatus页面,指定

取消 -任务用自己的的CancellationToken抛出OperationCanceledException承认取消,而令牌是信号状态, 或之前的任务开始执行任务的的CancellationToken已经发出信号

因此,在这一天结束T0将在RanToCompletion状态和C0将在Canceled状态。

这一切,当然,假设当前SynchronizationContext不允许任务同时运行(如你所知,Windows窗体一个没有-我只是指出这是不同步的上下文的要求)

此外,值得注意的是,确切的回答你关于取消标记是否在当请求取消或执行任务时的背景检查最后一个问题,答案是真的两者 。 除了在最后检查TryExecuteTask ,一旦被请求撤销该框架将调用TryDequeue ,可选的操作,任务调度支持。 同步执行绪调度器不支持它。 但是,如果它在某种程度上的确,区别可能是,“执行C0 ”的消息将被撕去了全部线程的消息队列,它甚至不会尝试执行任务。



Answer 2:

其中的线程来检查CencellationToken就我看来,不管,你必须考虑你的延续可以得到调度并在正在执行的延续,用户可以取消该请求的可能性。 所以我觉得这是注释掉,应检查并可能应该读取结果后再次检查检查:

        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());

我还要补充一个continutation单独处理的取消条件:

        task.ContinueWith(t =>
    {
        // Do whatever is appropriate here.

    }, ct, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());

这样,您就拥有所有的可能性覆盖。



文章来源: Race condition with CancellationToken where CancellationTokenSource is only cancelled on the main thread