Multiple Backgroundworker threads in a single Backgroundworker thread - multithreading

I have an issue here with multi-threading. Tried using both backgroundworker and threads.
Objective: Select an item from a combo-box and click a button to trigger multiple backup file restores into the MSSQL Server. The intention is to popup as many popups as there are Backup files to restore. The main window starts a backgroundworker, showing the overall progress, while the child threads result in non-modal child popups representing each restoration process with progress. This works fine if run serially (without threads/backgroundworker). My intention is to run a bunch of parallel popups, so that the restoration is much more quick.
Problem Statement: My intention is to run a bunch of parallel popups, so that the restoration is much more quick instead of running serially. While trying to debug, its a chaotic break-point show that the Visual Studio shows up (perhaps to represent multiple threads in parallel). But the ultimate goal is not being achieved. Can anybody help me in this regard?
- Code Extracts
Here is the code extract, which I've done and this works in a serial fashion. But as soon as I put the code for multi-threading, nothing works. Only the popups appear, but no processing happens.
This is the button click event, which starts the whole process
private void btnSnapshot_Click(object sender, EventArgs e)
{
this.SetPanelEnabledProperty(false); // Disable All Controls on the main window
// Select the text against which the DATABASE Backup Files are to be picked
// Start the main background worker process, which in turn will trigger few other child threads
BGWrk.RunWorkerAsync(2000);
BGWrk.DoWork += new DoWorkEventHandler(BGWrk_DoWork);
BGWrk.ProgressChanged += new ProgressChangedEventHandler(BGWrk_ProgressChanged);
BGWrk.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(BGWrk_RunWorkerCompleted);
BackgroundWorker helperBW = sender as BackgroundWorker;
BGWrk.WorkerReportsProgress = true;
BGWrk.WorkerSupportsCancellation = true;
}
private void BGWrk_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker helperBW = sender as BackgroundWorker;
int arg = (int)e.Argument;
//Call the method that invokes the popup window instances in a loop, for each file found for the SnapShotName selected in the combobox
ParallelRestoreSnapshot();
if (helperBW.CancellationPending)
{
e.Cancel = true;
}
}
// This method will create backgroundworker instances and in turn the Do_Work events of those backgroundworkers will call the popup against each backup file.
private bool ParallelRestoreSnapshot()
{
FileOperations FIO = new FileOperations();
if (SelectedSnapShot != null && SelectedSnapShot != "None")
{
string[] sBakFiles;
sBakFiles = FIO.GetListOfBackupFiles(sEntireBackupFilePath);
try
{
progressPopupsList = new List<FrmProgressPopup>();
for (int aIndex = 0; aIndex < sBakFiles.Length; aIndex++)
{
BackgroundWorker bgPopups = new BackgroundWorker();
BAKFileName = sBakFiles[aIndex];
bgPopups.RunWorkerAsync(2000);
bgPopups.DoWork += new DoWorkEventHandler(bgPopups_DoWork);
bgPopups.ProgressChanged += new ProgressChangedEventHandler(bgPopups_ProgressChanged);
bgPopups.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(bgPopups_RunWorkerCompleted);
bgPopups.WorkerReportsProgress = true;
bgPopups.WorkerSupportsCancellation = true;
}
retVal = true;
}
catch (Exception exc)
{
MessageBox.Show("Error while Restoring: " + exc.Message, "Exception encountered", MessageBoxButtons.OK, MessageBoxIcon.Error);
//goto NextDB;
return false;
}
}
FIO = null;
return retVal;
}
// This DoWork event calls a method GeneratePopupInstances which makes copies of a window, which is shown as non-modal
private void bgPopups_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker helperBW = sender as BackgroundWorker;
int arg = (int)e.Argument;
//BackgroundProcessLogicMethod(helperBW, arg);
GeneratePopupInstances();
if (helperBW.CancellationPending)
{
e.Cancel = true;
}
}

Related

UI thread slow to respond to Progress updaters on async Task method using VS2022 & Net6.0

I’ve run into a performance obstacle and I’m uncertain of the cause, all of this is running under VS2022 & Net6.0. As this is my 1st time using this combination of a modal windows form, and progress bar, with the work running on a background thread and two Progress objects updating the UI, the progress bar, and a text label, I don’t know where to attack the problem. Prior to placing the workload on a background thread, everything was snappy, searching a thousand files with about 600 lines of text in each, in about a minute. Naturally, the windows form was frozen during this, which is why the workload was placed on a background thread.
After doing so, the workload will be 25-50% complete before the UI starts displaying the values from the Progress objects, and overall, the entire process now takes 10x as long to complete. Progress objects aren’t skipping over any values sent to them, the UI thread just seems slow in getting the information. Likewise, if I try to drag the modal form to a new spot on the desktop it’s unresponsive for 20—30 seconds before it finally moves. One more thing, I can step through the code on the background thread and see it calling the Progress updaters, but the UI thread is just very slow in responding to them.
I could use some suggestions on how to uncover the problem or if clearly evident, point out where the likely problem could be. Here are the essential controls and methods used.
public class SearchProgressForm : Form
{
private System.Windows.Forms.Button btnSearch = new Button();
private System.Windows.Forms.TextBox txtTextSearch = new TextBox();
private System.Windows.Forms.Label lblSearchFile = new Label();
private System.Windows.Forms.ProgressBar SearchProgressBar = new ProgressBar();
public event LogSearchEventHandler SearchSucceededEvent;
protected void OnSearchSucceeded(LogSearchEventArguments p_eventArguments)
{
LogSearchEventHandler handler = SearchSucceededEvent;
if (handler != null)
{
handler(this, p_eventArguments);
}
}
private void InitializeComponent()
{
this.btnSearch.Name = "btnSearch";
this.btnSearch.Text = "Search";
this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click);
this.lblSearchFile.Text = "Searching File: ";
this.txtTextSearch.Text = "search string";
}
public SearchProgressForm() { }
private void btnSearch_Click(object sender, EventArgs e)
{
this.SearchByText(this.txtTextSearch.Text);
}
private void SearchByText(string p_searchParameter)
{
// Setup a progress report for thr ProgressBar
var _progressBarUpdate = new Progress<int>(value =>
{
this.SearchProgressBar.Value = value;
this.SearchProgressBar.Refresh();
});
var _progressFileNameUpdate = new Progress<string>(value =>
{
this.lblSearchFile.Text = "Searching File For : " + value;
this.lblSearchFile.Refresh();
});
// Start search on a backgroud thread and report progress as it occurs
Task.Run(async () => await this.SearchByStringAsync(p_searchParameter, _progressBarUpdate, _progressFileNameUpdate));
}
private async Task SearchByStringAsync(string p_searchParameter, IProgress<int> p_progressBar, IProgress<string> p_progressFileName)
{
await Task.Delay(1);
TextFileReader textFileReader = new TextFileReader();
LogSearchEventArguments logSearchEventArguments = null;
long _sessionloopCount = 0;
long _totalTextLinesCount = this.GetTotalSearchCount(p_searchParameter, SearchType.TextString);
// Get file names from SQL table
var _logFiles = DataOperations.LogFileSortableList(null);
foreach (var log in _logFiles)
{
// Format a file name to be read from the file system
string _fileName = log.Directory + "\\" + log.FileName;
p_progressFileName.Report(log.FileName);
// If we've raised an event for this file, then stop iterating over remaning text
if (logSearchEventArguments != null)
{
logSearchEventArguments = null;
break;
}
// Read in file contents from file system
List<string> _fileContents = textFileReader.ReadAndReturnStringList(_fileName);
long _fileTotalRecordCount = _fileContents.Count;
long _fileRecordCount = 0;
foreach (var _line in _fileContents)
{
if (_line.ToUpper().Contains(p_searchParameter.ToUpper()))
{
// Raise an event so search parameter and file name can be captured in another form
logSearchEventArguments =
new LogSearchEventArguments
(
"TextSearch", p_searchParameter, SearchType.TextString, true, log,
new DateTime(
Convert.ToInt32("20" + log.FileName.Substring(14, 2)),
Convert.ToInt32(log.FileName.Substring(16, 2)),
Convert.ToInt32(log.FileName.Substring(18, 2)))
);
// We found a match, so no further searching is needed in this log file,
// and it's been flagged in the DB, so raise the event to save search parameter and file name
// then break out of this loop to get the next file to search in.
this.OnSearchSucceeded(logSearchEventArguments);
break;
}
// These calcs are based on actual searches performed
_fileRecordCount++;
_sessionloopCount++;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
// Because we exit a search as soon as the 1st match is made, need to resynch all counts
// and update the progress bar accordingly
if (_fileRecordCount < _fileTotalRecordCount)
{
long _countDifference = _fileTotalRecordCount - _fileRecordCount;
// Add count difference to sessionLoopCount and update progress bar
_sessionloopCount += _countDifference;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
}
//Search is complete set Progress to 100% and report before exiting
p_progressBar.Report(100);
// Close the modal SearchForm and exit
this.Close();
}
}
I solved this problem but I'm still not certain of what caused it. I eliminated the method "private void SearchByText(string p_searchParameter)" and moved the code there into the btnSearch_Click event handler so I could call my background worker "SearchByStringAsync" directly from the button click event handler.
I also updated the EFCore NuGet Packages, which were version Net6.0 to version 6.0.4, because of single line of code in my Async background method, "var _logFiles = DataOperations.LogFileSortableList(null)".
That call returned a Sortable BindingList, using BindingList <T>. Between the NuGet updates and a minor change on a custom comparer method in my BindingList <T> class, the windows modal form now updates the ProgressBar and Label text as expected, and the form now responds immediately to user interaction.

UWP update UI control from a task running in a different thread

UI has a label named lblMyLabel.
It’s bound to a view model MyViewModel.
A task is running on a different thread which pulls message from a service.( for example)
When a this task receives a specific value it should update the UI label with it.
I always get a error that it was Marshalled in a different thread .
When you want to update UI,you should run on the UI's dispatcher thread.You can call Dispatcher.RunAsync to back onto the UI's dispatcher thread.
private void Button_Click(object sender, RoutedEventArgs e)
{
//Start a Task
Task t = Task.Factory.StartNew(() => {
int i = 0;
for (i = 0; i <= 1000; i++)
{
}
//Back onto the UI thread
var a = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//Update UI
MyTextBlock.Text = "Update";
});
});
}

Multithreaded program hangs on control method call

I've got a UI with 2 buttons and a textbox.
When the user presses the "Receive" button, a thread is started for an infinite work loop (receiving and printing messages from multicast). When the user presses the "Stop" button, the thread is given it's "kill flag" and then the program waits for the Join() to finish.
That infinite work loop calls back to the UI textbox with a SetTextBoxText(System::String) method. This is causing a race condition. Sometimes, the thread finishes the Join() just fine. Others, the program hangs forever on the Join().
I believe this is because when the UI thread calls Join() on its work thread, the work thread may be in the middle of its loop and is trying to invoke a locked thread. The UI thread is waiting for the Join() and is unable to do anything (like SetTextBoxText).
So, race-condition or deadlock, I need a way to check if the control is usable. If it is, then call the method to print like normal. If it is not usable, then don't try to call the SetTextBoxText method and continue like normal.
Code time, I'm actually using a UserControl, not a separate textbox, button1, and button2. I call the UserControl "MyUserControl". It contains the following code:
System::Void MyUserControl::buttonReceive_Click(System::Object^ sender, System::EventArgs^ e)
{
//each MyUserControl has their own myThread
this->myThread = gcnew System::Threading::Thread(gcnew System::Threading::ThreadStart(this, &MyUserControl::WorkLoop));
this->workLoopFlag = true;
this->myThread->Start();
}
void MyUserControl::WorkLoop()
{
while (this->workLoopFlag == true)
{
System::Threading::Thread::BeginCriticalRegion();
//this->myThread->BeginCriticalRegion();
Process();
//this->myThread->EndCriticalRegion();
System::Threading::Thread::EndCriticalRegion();
}
}
void EntityStatePduProcessor::Process()
{
//These 2 lines are for getting the correct MyUserControl to edit. Stored in GlobalVars::myList
ThreadPredicate^ threadPred = gcnew ThreadPredicate(System::Threading::Thread::CurrentThread->ManagedThreadId);
UserControlDataCollection^ userControlDataColl = GlobalVars::myList->Find(gcnew System::Predicate<UserControlDataCollection^>(threadPred, &ThreadPredicate::IsMatch));
System::Threading::Thread::BeginCriticalRegion();
if (userControlDataColl != nullptr) //should only happen after killing a thread
{
MyUserControl^ controlToEdit = (MyUserControl^)System::Windows::Forms::Control::FromHandle(userControlDataColl->control);
if (controlToEdit != nullptr)
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
controlToEdit->SetTextBoxConsoleText("a system::string to place into the textbox");
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
else
{
System::Windows::Forms::MessageBox::Show("controlToEdit == nullptr");
}
}
System::Threading::Thread::EndCriticalRegion();
}
delegate void MyStringDelegate(System::String ^ text);
void MyUserControl::SetTextBoxConsoleText(System::String^ input)
{
if (this->InvokeRequired) // (this->consoleTextBox->InvokeRequired)
{
MyStringDelegate^ myStrDel = gcnew MyStringDelegate(this, &MyUserControl::SetTextBoxConsoleText);
this->Invoke(myStrDel, gcnew array<Object^> { input });
}
else
{
this->textBoxConsole->Text = input;
this->textBoxConsole->Refresh();
}
}
System::Void MyUserControl::buttonStop_Click(System::Object^ sender, System::EventArgs^ e)
{
this->workLoopFlag = false; // kill the endless while loop
this->myThread->Join();
}
I tried to post the code in chronological order and only include the essentials while explaining the gaps in //comments.
The code that causes the hang is in the 3rd method shown Process() outlined with //~~~.
How do I correctly ensure that the controlToEdit is still able to access and not just waiting on a Join()?

C# Win Form - Main thread not responding when using background worker

I have a datagridview which is being populated by a DataTable through DataSource and i am using a backgroundworker for formatting of cells (back color and forecolor of cells) in datagridview.
BackgroundWorker bw = new BackgroundWorker();
private void Frm_Find_Load(object sender, EventArgs e)
{
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
CheckForValidValues();
}
public bool CheckForValidValues()
{
dgv.Invoke((MethodInvoker)delegate()
{
for (int i = 0; i < Dt.Rows.Count; i++)
{
if (Dt.Rows[0]["Name"].ToString() == Value)
{
dgv.Rows[i].Cells["Name"].Style.BackColor = Color.FromArgb(255, 192, 192);
}
else
{
dgv.Rows[i].Cells["Name"].Style.BackColor = Color.White;
}
progressBar1.Invoke((MethodInvoker)(() => progressBar1.Value++));
}
});
this.Invoke((MethodInvoker)delegate()
{
BtnShow.Enabled = true;
dgv.Enabled = true;
});
}
private void btnSave_Click(object sender, EventArgs e)
{
if (bw.IsBusy == false)
{
progressBar1.Visible = true;
progressBar1.Value = 0;
BtnShow.Enabled = false;
dgv.Enabled = false;
progressBar1.Maximum = dgv.Rows.Count;
bw.RunWorkerAsync();
}
}
while the whole process goes down the DataGridView remains Enabled=false so that user cant change any values in datagridview.
There is usually 15,000 rows in the datagridview and that many rows to format, this takes time that is why I use a backgroundworker for it and it works perfectly fine BUT when the user tries to press the enabled false datagridview couple of times, the main thread becomes unresponsive and the progressbar freezes.
Can anyone guide me how to deal with it?
You are running the whole code using Invoke. It means you are switching to UI thread and the code is running in UI thread. Since the code is a time-consuming for loop, then it's blocking UI thread.
Instead of using a BackgroundWorker, to format cells, it's better to use CellFormatting event:
private void dgv_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
//If this is header cell or new row, do nothing
if (e.RowIndex < 0 || e.ColumnIndex < 0 || e.RowIndex == dgv.NewRowIndex)
return;
//If formatting your desired column, perform validation
if (e.ColumnIndex == dgv.Columns["Name"].Index)
{
// Perform validation and change cell back color here.
}
}
Maybe try to disable the Button when it get clicked until the job done?.
Lets say your Button name is Button1,
when the user click on the Enabled false data grid view, use Button.enabled=false, then when the job is done use Button.enabled=true
hope that helps!

Error Cross-thread operation not valid: Control 'CameraViewVS' accessed from a thread other than the thread it was created on. parallel.for

I have a timer to verify one condition every time and show pop up form only once if the condition is verified. I want to verify in parallel all instances, so i used parallel.for, but i have this error "Cross-thread operation not valid: Control 'CameraViewVS' accessed from a thread other than the thread it was created on." in line " frm.WindowState = FormWindowState.Normal;"
this is my code:
public void timer1_Tick(object source, EventArgs e)
{
Parallel.For(0, nbre, l =>
{
cameraInstanceList[l].Start();
if (cameraInstanceList[l].MoveDetection == true)
{
//show the the form S once
foreach (Form S in Application.OpenForms)
{
var frm = S as Formes.CameraViewVS;
if (frm != null && frm.IP == cameraInstanceList[l].adresse)
{
cameraInstanceList[l].MoveDetection = false;
frm.WindowState = FormWindowState.Normal;
frm.Activate();
return;
}
}
f1 = new Formes.CameraViewVS(cameraInstanceList[l],
adresseIPArray[l]);
f1.Show(this);
}
}
);
Most properties on WinForm object instances need to be accessed from the thread that they were created on. You can use the Control.InvokeRequired property to determine if you need to use the control (or form) Invoke method to execute the code on the UI thread.
It is also a good practise to create most WinForm controls on the main UI thread, and not on any thread pool threads. In WinForms applications, you can use the SynchronizationContext to ensure some code, such as creating a form, is called on the UI thread.
EDIT: changed so that the method doesn't return after movement detected.
public void timer1_Tick(object source, EventArgs e)
{
// assume this is being called on the UI thread, and save the thread synchronization context
var uiContext = SynchronizationContext.Current;
Parallel.For(0, nbre, l =>
{
while (true)
{
Thread.Sleep(250); // <--- sleep for 250 ms to avoid "busy" wait
cameraInstanceList[l].Start();
if (cameraInstanceList[l].MoveDetection == true)
{
// capture instances used in closures below
var cameraInstance = cameraInstanceList[l];
var ipAdresse = adresseIPArray[l];
//show the the form S once
foreach (Form S in Application.OpenForms)
{
var frm = S as Formes.CameraViewVS;
if (frm != null)
{
// create delegate to be invoked on form's UI thread.
var action = new Action(() =>
{
if (frm.IP == cameraInstance.adresse)
{
cameraInstance.MoveDetection = false;
frm.WindowState = FormWindowState.Normal;
frm.Activate();
}
};
if (frm.InvokeRequired)
frm.Invoke(action);
else
action();
continue; // <--- go back to the top of the while loop
// and wait for next detection
}
}
// create delegate to create new form on UI thread.
var createNewFormCallback = new SendOrPostCallback((o) =>
{
f1 = new Formes.CameraViewVS(cameraInstance, ipAdresse);
f1.Show(this);
};
// and invoke the delegate on the ui thread
uiContext.Send(createNewFormCallback, null);
}
}
}
);
}
Thomas is very close to right answer ,Because Every Control runs in a different thread .You should just write a code for context-switching of resources which is being used by Controls
Thread ..Don't worry you have a lot of facility for this in c sharp.Just use BeginInvoke and Invoke and i hope you would be able to resolve your problem.Write this in place of your old code block ..
var action = new Action(() =>
{
if (frm.IP == cameraInstance.adresse)
{
cameraInstance.MoveDetection = false;
frm.WindowState = FormWindowState.Normal;
frm.Activate();
}
};
if (frm.InvokeRequired)
frm.BeginInvoke(action);
else
frm.Invoke(action);

Resources