【问题标题】:How can I mitigate a race condition between the UI thread and a DispatcherTimer.Tick event?如何缓解 UI 线程和 DispatcherTimer.Tick 事件之间的竞争条件?
【发布时间】:2018-04-23 23:40:34
【问题描述】:

我相信我在此代码示例中存在竞争条件,但不确定如何缓解它。

我的情况是 XAsync() 总是在 UI 线程上运行。在 XAsync() 中,我设置了 m_importantMemberVariable,然后启动了一个计时器;在计时器触发之前有 1 秒的延迟。

我担心的是计时器的滴答事件会调用 m_importantMemberVariable 上的方法。但是,在启动计时器和触发 Tick 之间的 1 秒间隔内,可以再次调用 XAsync() 并覆盖 m_importantMemberVariable。

代码示例:

task<void> BobViewModel::XAsync()
{
    return create_task(CreateSomethingAsync())
        .then([this](SomethingAsync^ aThing)
    {
        this->m_importantMemberVariable = aThing;
        OnPropertyChanged("ImportantMemberVariable");

        // Timer has 1 second delay.
        this->m_myDispatcherTimer->Start();
    }, task_continuation_context::use_current())
        .then([activity](task<void> result)
    {
        // more continuations...
    });
}

void BobViewModel::OnTimerTick(Object^, Object^)
{
    // Stopping the timer and detaching the event handler
    // so timer only fires once.
    m_myDispatcherTimer->Stop();
    m_myDispatcherTimer->Tick -= m_impressionTimerToken;
    m_myDispatcherTimer = { 0 };

    // * Possible race condition *
    m_importantMemberVariable->DoImportantThing();
}

问题:假设我对竞争条件的看法是正确的,有没有办法缓解它?

我的理解是,tick 事件会在 UI 线程上触发,因此同步原语将无济于事(因为 UI 线程已经拥有访问权限)。

【问题讨论】:

  • 没有种族。唯一可能出错的是 DoImportantThing() 将在更新 m_importantMemberVariable 后不到 1 秒内被调用。代码在调用 Start() 之前忘记了 Stop() 计时器。
  • @HansPassant - 如果 XAsync 的第二个调用者在计时器启动后(但在触发滴答事件之前)使 m_importantMemberVariable 为空,那怎么不会产生空引用异常?你是说这不可能发生吗?如果有,为什么?
  • 如果您遵循建议,则您已停止计时器。当变量为空时,为什么要再次调用 Start()?
  • 这似乎是对你已经问过两次我回答的问题的欺骗。
  • @PeterTorr-MSFT - 不同之处在于这个问题将 DispatcherTimer 混入其中,因此它不仅限于任务延续。

标签: multithreading uwp task uwp-xaml


【解决方案1】:

您的所有操作都在 UI 线程上,因此它们已经为您序列化(同步)​​了。一个简单的标志就足够了:

bool m_busy; // set to false in constructor

task<void> BobViewModel::XAsync()
{
    if (m_busy)
      return;

    m_busy = true;
    // the rest of your code...
}

void BobViewModel::OnTimerTick(Object^, Object^)
{
    m_busy = false;
    // the rest of your code...
}

只要确保你处理任何异常,如果出现严重错误,你将 m_busy 设置回 false

【讨论】:

    【解决方案2】:

    this question 的答案建议将 compare_exchange_strong 与 std::atomic 一起使用,以确保一次只有一个线程执行一个函数。对于这个问题,这种方法的问题是:
    1. DispatcherTimer Tick 事件在任务延续块外触发,可以在延续完成后触发。
    2. 这个问题的一个限制是定时器只能触发一次。

    一些替代解决方案是:

    1. 使用 compare_exchange_strong 但将 DispatcherTimer 替换为 create_delayed_task

    假设工作不必在 UI 线程上进行,您可以使用 create_delayed_task 在任务继续内延迟工作。

    task<void>
    BobViewModel::UseImportantVariableAsync(
        Object^ importantVariable
    )
    {
        return create_delayed_task(
            std::chrono::milliseconds(1000),
            [importantVariable]()
        {
            importantMemberVariable->DoImportantThing();
        });
    }
    

    那么,从任务延续,简单来说:

    return UseImportantVariableAsync(m_importantMemberVariable);
    
    1. 为 DispatcherTimer 的 Tick 事件使用 lambda,并从问题示例中捕获“aThing”(而不是在处理程序中引用成员变量)。要仅触发一次计时器,请在 std::call_once 块中分配 DispathcerTimer.Tick 处理程序,以便只有第一个调用者可以执行此操作。

    【讨论】:

      猜你喜欢
      • 2016-11-16
      • 2013-06-13
      • 1970-01-01
      • 2019-09-19
      • 1970-01-01
      • 1970-01-01
      • 2016-02-02
      • 2013-11-14
      • 1970-01-01
      相关资源
      最近更新 更多