如何减轻UI线程和DispatcherTimer.Tick事件之间的竞争条件?

时间:2018-04-23 23:40:35

标签: multithreading uwp task uwp-xaml

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

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

我担心的是计时器的tick事件会调用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();
}

问题:假设我对比赛情况有所了解,有没有办法减轻它?

我的理解是会在UI线程上触发tick事件,因此同步原语不会有帮助(因为UI线程已经具有访问权限)。

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

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

一些替代解决方案是:

  1. 使用compare_exchange_strong但将DispatcherTimer替换为create_delayed_task
  2. 假设工作不必在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. 将lambda用于DispatcherTimer的Tick事件,并从问题示例中捕获'aThing'(而不是在处理程序中引用成员变量)。要仅触发计时器一次,请在std::call_once块中分配DispathcerTimer.Tick处理程序,以便只有第一个调用者才能执行此操作。