Windows 形式:后台工作者同步和管理

Windows Forms: background worker synchronization and management

我在将以下非常简化的案例作为我的项目的一部分时遇到问题。考虑我们有如下的 GUI:

我有两个后台工人:

我也有 label_timer,它更新了我表单上显示的增量值。

为了同时管理后台工作者和计时器,我写了两个函数:

private: void turnOnAcquisition() {
    if (!counting_paused)
        return;

    if (!plot_bgworker->IsBusy)
        plot_bgworker->RunWorkerAsync();
    if (!data_bgworker->IsBusy)
        data_bgworker->RunWorkerAsync();

    label_timer->Enabled = true;
    counting_paused = false;
}

private: void turnOffAcquisition() {

    if (counting_paused)
        return;

    if (plot_bgworker->IsBusy)
        plot_bgworker->CancelAsync();
    if (data_bgworker->IsBusy)
        data_bgworker->CancelAsync();

    label_timer->Enabled = false;
    counting_paused = true;
 }

然后,当我单击每个按钮时会发生以下情况:

// Pauses counting on click
private: System::Void stop_btn_Click(System::Object^  sender, System::EventArgs^  e) {
    turnOffAcquisition();
}

// Starts counting on click
private: System::Void start_btn_Click(System::Object^  sender, System::EventArgs^  e) {
    turnOnAcquisition();
}

// Should restart counting on click, beginning from 0 (no matter what state counting is in right now)
private: System::Void restart_btn_Click(System::Object^  sender, System::EventArgs^  e) {
    plot_counter = 0;
    data_counter = 0;
    turnOffAcquisition();
    turnOnAcquisition();
}

最后,这是我的后台工作程序(由 CancelAsync() / RunWorkerAsync() 关闭/打开)和计时器:

// Calculating data counter
private: System::Void data_bgworker_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e) {
    for (;;) {          
        data_counter++;
        Sleep(50);
        if (data_bgworker->CancellationPending) {
            e->Cancel = true;
            return;
        }
    }
}

// Calculating plot counter
private: System::Void plot_bgworker_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e) {
    for (;;) {
        plot_counter++;
        Sleep(120);
        if (plot_bgworker->CancellationPending) {
            e->Cancel = true;
            return;
        }
    }
}

// Display counters
private: System::Void label_timer_Tick(System::Object^  sender, System::EventArgs^  e) {
    plot_counter_label->Text = numToMStr(plot_counter);
    data_counter_label->Text = numToMStr(data_counter);
}

开始按钮和停止按钮都按预期工作,但现在我的重启按钮有问题。当我在计数过程中单击它时,它似乎会重置值并停止后台工作程序,但再也不会启动它们(正如我在调用 turnOnAcquisition 后所期望的那样)。但是,当我在计数关闭时单击它时,我能够按预期打开计数。

我的第一个镜头是,当我试图检查我的工作人员是否忙碌时,取消标志尚未设置为另一个值,但在调用之间使用 Sleep() 不起作用。另一种猜测是由于竞争条件失败,所以我尝试使用 MemoryBarrier(),但我不知道这些库,我不确定它是否有效。另外,我尝试使用 Interlocked class,但无法将其正确用于 void 函数。

1.这种思路正确吗?
2. 如果是,为什么简单的 Sleep() 不能解决问题?
3. 在这种情况下,我将如何使用上述任何一种方法,哪种方法最合适?

好的,我自己找到了解决方案。这里的问题是关于竞争条件 - 一个事件试图停止计数(这意味着引发另一个事件)然后再次开始它(这是有问题的,因为我的功能(我猜)已经被第一个和可能的第二个弄乱了事件甚至没有添加到事件检测队列中)。如果我的解释有误,请多多批评 ;)

这里修改了两个函数,正确解决了线程管理问题。关键是让其他事件完成它们的工作,直到我获得所需的状态。

当我想关闭计数时,我让应用程序执行队列中的事件,直到两个线程都不忙('while' 循环):

private: void turnOffAcquisition() {

    if (counting_paused)
        return;

    if (plot_bgworker->IsBusy)
        plot_bgworker->CancelAsync();
    if (data_bgworker->IsBusy)
        data_bgworker->CancelAsync();

    while((plot_bgworker->IsBusy) || (data_bgworker->IsBusy))   // Continue to process events until both workers stop working
        Application::DoEvents();                                // Then, you can process another thread requests! :)

    label_timer->Enabled = false;
    counting_paused = true;
 }

同样,当我想重新开始计数时,我让应用程序执行事件,直到我检查两个线程都忙(同样,'while' 循环):

private: void turnOnAcquisition() {
    if (!counting_paused)
        return;

    if (!plot_bgworker->IsBusy)
        plot_bgworker->RunWorkerAsync();
    if (!data_bgworker->IsBusy)
        data_bgworker->RunWorkerAsync();

    while((!plot_bgworker->IsBusy) || (!data_bgworker->IsBusy)) // Continue to process events until both workers start working
        Application::DoEvents();                                // Then, you can process another thread requests! :)

    label_timer->Enabled = true;
    counting_paused = false;
}