I have a problem with following, very simplified case being part of my project. Consider we have GUI like below:
I have two background workers:
- plot_bgworker - in this example, it increments plot counter,
- data_bgworker - in this example, it increments data counter.
I also have label_timer, which updates incremented values diplayed on my form.
To manage both background workers and timer, I wrote two functions:
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;
}
Then, here is what happens when I click each of my buttons:
// 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();
}
Finally, here are my background workers (turned off / on by CancelAsync() / RunWorkerAsync() ) and timer:
// 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);
}
Start button and stop button work both as expected, but now I have a problem with restart button. When I click it in the middle of counting, it seems to reset values and stop background workers, but never start them again (as I would expect after calling turnOnAcquisition). However, when I click it when counting is off, I am able to turn on counting as expected.
My first shot was that cancellation flag is not yet set to another value when I tried to check if my workers were busy, but using Sleep() between calls didn't work. Another guess is that it is due to race condition failure, so I tried using MemoryBarrier(), but I don't know the libraries and I'm not sure if it would work. Also, I tried to use Interlocked class, but couldn't use it properly for void functions.
1. Is this way of thinking correct?
2. If yes, why simple Sleep() doesn't do the trick?
3. How would I use any of mentioned methods in this case and which one would be the best match?
Ok, I found the solution by myself. The problem here was about the race condition - one event tried to stop counting (which meant raising another event) and then starting it again (which was problematic, as my function (I guess) was already cluttered with the first one and probably the second event wasn't even added to the event detected queue). If I am wrong with the explanation, I would appreciate some criticism down there ;)
Here are two modified functions, which solved thread management correctly. The key was to let the other events do their work until I get desired state.
When I want to turn off counting, I let the applications do the events from the queue until both threads will not be busy (the 'while' loop):
Similarily, when I want to restart counting, I let the application do the events until I check that both threads are busy (again, the 'while' loop):