用的CancellationToken竞争状态,其中CancellationTokenSource只取消了主线程 [英] Race condition with CancellationToken where CancellationTokenSource is only cancelled on the main thread

查看:216
本文介绍了用的CancellationToken竞争状态,其中CancellationTokenSource只取消了主线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑一个WinForms应用程序,在这里我们有一个生成一些成果的按钮。如果用户按下按钮的第二时间,就应该取消,以产生结果,并开始一个新的第一请求。

Consider a Winforms application, where we have a button that generates some results. If the user presses the button a second time, it should cancel the first request to generate results and start a new one.

我们正在使用下面的模式,但我们不能确定,如果一些代码是必要的,以防止竞争条件(见注释行)。

We're using the below pattern, but we are unsure if some of the code is necessary to prevent a race condition (see the commented out lines).

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

  • We only ever cancel the CancellationTokenSource on the main thread.
  • We use the same CancellationToken in the continuation as we do in the original task.

我们想知道是否还是不活动的下列顺序是可能的:

We're wondering whether or not the following sequence of events is possible:


  1. 用户点击生成结果按钮。初始任务T1开始。

  2. 用户点击一次产生效果按钮。的Windows消息发布到队列中,但处理尚未执行。

  3. 任务T1完成。

  4. TPL <击>启动准备启动的延续(因为的CancellationToken 尚未取消)。任务调度岗位工作到Windows消息队列(得到它的主线程上运行)。

  5. 的generateResultsButton_Click为第2点击开始执行与 CancellationTokenSource 将被取消。

  6. 的延续工作,开始和它的运作就好像令牌没有取消(即它显示在用户界面的结果)。

  1. User clicks "generate results" button. Initial task t1 is started.
  2. User clicks "generate results" button again. Windows message is posted to queue, but the handler hasn't been executed yet.
  3. Task t1 finishes.
  4. TPL starts prepares to start the continuation (since the CancellationToken is not cancelled yet). The task scheduler posts the work to the Windows message queue (to get it to run on the main thread).
  5. The generateResultsButton_Click for the 2nd click starts executing and the CancellationTokenSource is cancelled.
  6. The continuations work starts and it operates as though the token were not cancelled (i.e. it displays its results in the UI).

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

So, I think the question boils down to:

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

When work is posted to the main thread (by using TaskScheduler.FromCurrentSynchronizationContext()) does the TPL check the CancellationToken on the main thread before executing the task's action, or does it check the cancellation token on whatever thread it happens to be on, and then post the work to the SynchronizationContext?

推荐答案

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

Assuming I read the question correctly, you are worried about the following sequence of events:


  1. 单击该按钮时,任务 T0 定于线程池,延续 C0 计划为 T0 的延续要在同步上下文的任务调度运行

  2. 按钮再次点击。比方说,消息泵是忙着做别的事情,所以现在的消息队列由一个项目,点击处理程序。

  3. T0 完成后,这将导致 C0 被张贴到消息队列。现在的队列包含两个项目,单击处理程序和 C0 的执行。

  4. 单击处理消息泵,以及处理程序信号令牌驾驶 T0 C0 取消。然后,它的时间表 T1 线程池和 C1 在相同的方式与步骤 1 。

  5. 在'执行 C0 的消息仍然在排队,所以它得到处理现在。它是否执行您打算取消延续?

  1. The button is clicked, task T0 is scheduled on the thread pool, continuation C0 is scheduled as a continuation of T0, to be run on the synchronization context's task scheduler
  2. The button is clicked again. Let's say the message pump is busy doing something else, so now the message queue consists of one item, the click handler.
  3. T0 completes, this causes C0 to be posted to the message queue. The queue now contains two items, the click handler and the execution of C0.
  4. The click handler message is pumped, and the handler signals the token driving the cancellation of T0 and C0. Then it schedules T1 on the thread pool and C1 as a continuation in the same manner as step 1.
  5. The 'execute C0' message is still in the queue, so it gets processed now. Does it execute the continuation you intended to cancel?

答案是否定的。 TryExecuteTask 将不会执行任务,被通知注销。它是由该文档暗示,但在 TaskStatus明确规定了页面,指定

The answer is no. TryExecuteTask will not execute a task which has been signaled for cancellation. It's implied by that documentation, but spelled out explicitly on the TaskStatus page, which specifies

取消的 - 任务通过抛出OperationCanceledException承认其取消自己的CancellationToken而令牌信号状态中,或任务的的CancellationToken面前的任务开始执行

Canceled -- The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken while the token was in signaled state, or the task's CancellationToken was already signaled before the task started executing.

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

So at the end of the day T0 will be in the RanToCompletion state and C0 will be in the Canceled state.

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

This is all, of course, assuming that the current SynchronizationContext does not allow tasks to be run concurrently (as you are aware, the Windows Forms one does not -- I'm just noting that this is not a requirement of synchronization contexts)

另外,值得一提的是,确切的回答你关于取消标记是否是当请求撤销或者上下文检查最后一个问题任务执行时,答案是真的两个的。除了最后的检查在 TryExecuteTask ,尽快取消请求该框架将调用 TryDequeue ,可选的操作该任务调度程序能够支持。该同步上下文调度程序不支持它。但是,如果它在某种程度上确实,区别可能是,执行 C0 的消息会被完全撕开了线程的消息队列中,它甚至不会尝试执行任务。

Also, it's worth noting that the exact answer to your final question about whether the cancellation token is checked in the context of when cancellation is requested or when the task is executed, the answer is really both. In addition to the final check in TryExecuteTask, as soon as cancellation is requested the framework will call TryDequeue, an optional operation that task schedulers can support. The synchronization context scheduler does not support it. But if it somehow did, the difference might be that the 'execute C0' message would be ripped out of the thread's message queue entirely and it wouldn't even try to execute the task.

这篇关于用的CancellationToken竞争状态,其中CancellationTokenSource只取消了主线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆