Multithreading - Interrupt Thread upon WinForms Event - c#

I'm working on a C# WinForms project which implements a password manager. One of the features I want to include is a timeout for how long a password is allowed to stay in the system clipboard. I implemented a thread which updates a progress bar & then clears the clipboard before the thread terminates:
private void getPassword(int lifeInSeconds)
{
int maxLifeBarValue = lifeInSeconds * 10;
Thread t = new Thread
(delegate ()
{
//Initialize the progress bar
Invoke((MethodInvoker)delegate
{
lifeBar.Maximum = maxLifeBarValue;
lifeBar.Value = maxLifeBarValue;
lifeBar.Visible = true;
Clipboard.SetText(pd.getAccountPassword(lstAccounts.Text));
});
//Loop to update the progress bar
for (int x = maxLifeBarValue; x >= 0; x--)
{
Thread.Sleep(100);
Invoke((MethodInvoker)delegate
{
lifeBar.Value = x;
});
}
//Clear the system clipboard
Clipboard.SetText(string.Empty);
//Hide the progress bar when we're done
Invoke((MethodInvoker)delegate
{
lifeBar.Visible = false;
});
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
This works, but the problem I'm having is that if the user triggers an event to copy another password (or the same one; it doesn't matter), we now have 2 threads running in the background. This is apparent by the fact that the progress bar is "flipping out" so to speak as each thread is updating it's value independently.
Is there a way in which I can detect & terminate the original thread (if it exists) when the user clicks the copy password button again?

You could keep a reference to the Thread, and then abort the thread before starting a new one. Like this:
private Thread passwordClearThread = null;
private void getPassword(int lifeInSeconds)
{
int maxLifeBarValue = lifeInSeconds * 10;
if (passwordClearThread != null && passwordClearThread.IsAlive)
{
passwordClearThread.Abort();
passwordClearThread.Join();
}
passwordClearThread = new Thread
(() =>
{
//Initialize the progress bar
Invoke((MethodInvoker)delegate
{
lifeBar.Maximum = maxLifeBarValue;
lifeBar.Value = maxLifeBarValue;
lifeBar.Visible = true;
Clipboard.SetText(pd.getAccountPassword(lstAccounts.Text));
});
//Loop to update the progress bar
for (int x = maxLifeBarValue; x >= 0; x--)
{
Thread.Sleep(100);
Invoke((MethodInvoker)delegate
{
lifeBar.Value = x;
});
}
//Clear the system clipboard
Clipboard.Clear();
//Hide the progress bar when we're done
Invoke((MethodInvoker)delegate
{
lifeBar.Visible = false;
});
});
passwordClearThread.SetApartmentState(ApartmentState.STA);
passwordClearThread.Start();
}

Related

Why ProgressBar does not displaying the current value / does not refreshing?

I use the following code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
progressBar1.Value = 0;
progressBar1.Step = 1;
progressBar1.Maximum = 100;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
progressBar1.PerformStep();
label1.Text = (i + 1).ToString();
this.Refresh();
}
}
}
But, even after this.Refresh(); the value of the progress bar does not updated. Only the label updated. When the labels already show 100, for progress bar stil have more few steps to finish.
What i do wrong?
why the value of the progress bar is not updated?
How i should do it correct?
are you using Task, async, await? this is a common sample in winforms
see IProgress
public void DoWork(IProgress<int> progress)
{
// This method is executed in the context of
// another thread
for (int j = 0; j < 100000; j++)
{
//DO something
// Use progress to notify UI thread that progress has
// changed
if (progress != null)
progress.Report((j + 1) * 100 / 100000);
}
}
private async void button_Click(object sender, EventArgs e)
{
progressBar.Maximum = 100;
progressBar.Step = 1;
var progress = new Progress<int>(v =>
{
// This lambda is executed in context of UI thread,
// so it can safely update form controls
progressBar.Value = v;
});
// Run operation in another thread
await Task.Run(() => DoWork(progress));
}
I tried your code and it worked fine for me, did you add any special properties to your progress bar?
Assuming that it is all there is to it, try removing it and adding a new one without adjusting its default properties, you can also try adjusting the value in your Thread.Sleep() so that you can see the progress more

Unresponsive show dialog window

I am showing a modal dialog with my background worker, but it's becoming unresponsive when some long task is done on completed event of worker thread. Can someone tell me why it is happening and how I can fix this?
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var back = new BackgroundWorker();
back.DoWork += delegate
{
int i = 0;
while (i < 100)
{
Thread.Sleep(100);
i++;
}
};
back.RunWorkerCompleted += delegate
{
//running along task on UI therad
int i = 0;
while (i < 10000)
{
int j = 0;
while (j<10000)
{
label.Content = i.ToString();
j++;
}
i++;
}
msgbox.Close();
};
back.RunWorkerAsync();
msgbox.Title = "loading";
msgbox.Owner = this;
msgbox.ShowDialog();
}
You can't run any long-running method on the UI thread without blocking it. A single thread cannot both execute your while loop(s) and respond to user input simultaneously. This is impossible.
That's why you should execute any long-running method on a background, i.e. in the DoWork event handler in this case.
The RunWorkerCompleted event handler should only perform some quick UI related tasks, like for example updating a label or similar. Don't do anything heavy in there because then you will block the UI thread and your application will become unresponsive.
This part is executed in the UI thread:
back.RunWorkerCompleted += delegate
{
//running UI therad
int i = 0;
while (i < 100)
{
Thread.Sleep(100);
label.Content = i.ToString();
i++;
}
msgbox.Close();
};
When you call Thread.Sleep(100); you put the whole UI thread to sleep. Meaning, that your UI will sleep for 10 seconds.

Wait until panel controls are drawn

I have a form, this form has a panel. When the form OnShown fires, I start a background worker to execute a long running code method a set number of times. In this background worker, I add a User Control to the panel one-by-one/per-iteration while displaying a Wait picture on the UI thread.
Problem: My long running code runs faster than the panel can draw the controls I've added to it. Basically, when my form loads, it takes about 1.5 seconds to run the code (I can tell by how fast the wait picture hides itself), however, the child controls on the panel take between 4 and 6 seconds to draw themselves...
As you can imagine this would cause some major confusion to the user in that the Wait image would hide/the code would be done, but the controls wouldn't be shown for another 4 seconds.
So, is there a way to essentially "Wait" until all the child controls on the panel are drawn, so that i can ONLY THEN hide the Wait picture through some mechanism in the Panel control?
If that's not possible, keep in mind that I'm using a User Control and have access to those events as well. So if there's something there I could use to fire AFTER the User Control has drawn itself (or thinks it has), maybe i could use that instead and count until I reach the known count?
Here's my code, obscured a bit to not show any work info/not tell you what I'm doing but should be enough for you to find out if there's something wrong with the code itself...
Legend:
SW() = Show Waiting Picture,
HW() = Hide Waiting Picture,
this.wait = Wait Picture Control with progress bars...
private void MyForm_Shown(object sender, EventArgs e)
{
if (this.SomeList.Count <= 0 || this.SomeObject == null)
this.DialogResult = DialogResult.Abort;
SW();
this.wait.Text = "Loading everything...";
this.wait.TotalBar_Max = this.SomeList.Count;
this.wait.TotalBar_Value = 0;
this.wait.CurrentBar_Max = 100;
BackgroundWorker bkg = new BackgroundWorker();
bkg.DoWork += new DoWorkEventHandler(bkg_DoWork);
bkg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bkg_RunWorkerCompleted);
bkg.RunWorkerAsync();
}
void bkg_DoWork(object sender, DoWorkEventArgs e)
{
foreach (SomeType SomeItem in this.SomeList)
{
//Update Progress bar...
this.Invoke(new MethodInvoker(delegate
{
this.wait.Text = "Loading Specifics for: " + SomeItem.ToString();
this.wait.CurrentBar_Value = 50;
}));
MyControl tmp = new MyControl();
tmp.Name = SomeItem.ToString();
tmp.Text = SomeItem.ToString() + " - " + this.SomeObject.Name;
ServerWorker work = new ServerWorker(SomeItem);
Dictionary<string, List<string>> test = work.LongRunningCode();
//fill in/Init User Control based on long running code results...
if (test["Key1"] != null && test["Key1"].Count > 0)
{
test["Key1"].Sort();
tmp.Key1 = test["Key1"];
}
else
{
tmp.Key1_Available = false;
}
if (test["Key2"] != null && test["Key2"].Count > 0)
{
test["Key2"].Sort();
tmp.Key2 = test["Key2"];
}
else
{
tmp.Key2_Available = false;
}
if (test["Key3"] != null && test["Key3"].Count > 0)
{
test["Key3"].Sort();
tmp.Key3 = test["Key3"];
}
else
{
tmp.Key3_Available = false;
}
//Add user control, and update progress bars...
this.Invoke(new MethodInvoker(delegate
{
if (this.panel1.Controls.Count <= 0)
{
tmp.Top = 5;
tmp.Left = 5;
}
else
{
tmp.Top = this.panel1.Controls[this.panel1.Controls.Count - 1].Bottom + 5;
tmp.Left = 5;
}
this.panel1.Controls.Add(tmp);
this.wait.Text = "Loading Specifics for: " + SomeItem.ToString();
this.wait.CurrentBar_Value = 100;
this.wait.TotalBar_Value += 1;
this.panel1.Refresh();
}));
}
e.Result = true;
}
void bkg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
HW();
}
I found my own solution here:
https://stackoverflow.com/a/6653042/1583649
By building a List within the background worker instead of Invoking the UI thread, and then Panel.Add() on the RunWorkerCompleted event instead, the Wait image was able to stay alive until all the controls were actually drawn on the Panel.

C#, winform - Spinning Wheel progress breaks and resumes intermittently

Showing a Spin wheel progress animated gif while user initiates a long running process.
When i click the start, the process starts and same time wheel starts rotating.
But the problem is, the wheel strucks in-between and resumes, that happen multiple times during the long run process. It should be continuously rotation. I am running both the task and animated gif in same thread (since the indicator is just an animated image not a real progress value).
Code used is,
this.progressPictureBox.Visible = true;
this.Refresh(); // this - an user controll
this.progressPictureBox.Refresh();
Application.DoEvents();
OnStartCalibration(); // Starts long running process
this.progressPictureBox.Visible = false;
OnStartCalibration()
{
int count = 6;
int sleepInterval = 5000;
bool success = false;
for (int i = 0; i < count; i++)
{
Application.DoEvents();
m_keywordList.Clear();
m_keywordList.Add("HeatCoolModeStatus");
m_role.ReadValueForKeys(m_keywordList, null, null);
l_currentValue = (int)m_role.GetValue("HeatCoolModeStatus");
if (l_currentValue == 16)
{
success = true;
break;
}
System.Threading.Thread.Sleep(sleepInterval);
}
}
How do I show uninterrupted continuous display of wheel till the process ends?
If you use framework 4, replace the OnStartCalibration(); // Starts long running process line with the following code:
BackgroundWorker bgwLoading = new BackgroundWorker();
bgwLoading.DoWork += (sndr, evnt) =>
{
int count = 6;
int sleepInterval = 5000;
bool success = false;
for (int i = 0; i < count; i++)
{
Application.DoEvents();
m_keywordList.Clear();
m_keywordList.Add("HeatCoolModeStatus");
m_role.ReadValueForKeys(m_keywordList, null, null);
l_currentValue = (int)m_role.GetValue("HeatCoolModeStatus");
if (l_currentValue == 16)
{
success = true;
break;
}
System.Threading.Thread.Sleep(sleepInterval);
}
};
bgwLoading.RunWorkerAsync();
You can't run the progress indication and the task on the same thread. You should use a BackgroundWorker
Your GUI thread will subscribe to the ProgressChanged event, and will be notified of updates to the task. From here, you can update the progress indication appropriately. There's also events for when the task is finished.

How do I implement a progress bar in C#?

How do I implement a progress bar and backgroundworker for database calls in C#?
I do have some methods that deal with large amounts of data. They are relatively long running operations, so I want to implement a progress bar to let the user know that something is actually happening.
I thought of using progress bar or status strip label, but since there is a single UI thread, the thread where the database-dealing methods are executed, UI controls are not updated, making the progress bar or status strip label are useless to me.
I've already seen some examples, but they deal with for-loops, ex:
for(int i = 0; i < count; i++)
{
System.Threading.Thread.Sleep(70);
// ... do analysis ...
bgWorker.ReportProgress((100 * i) / count);
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = Math.Min(e.ProgressPercentage, 100);
}
I'm looking for better examples.
Some people may not like it, but this is what I do:
private void StartBackgroundWork() {
if (Application.RenderWithVisualStyles)
progressBar.Style = ProgressBarStyle.Marquee;
else {
progressBar.Style = ProgressBarStyle.Continuous;
progressBar.Maximum = 100;
progressBar.Value = 0;
timer.Enabled = true;
}
backgroundWorker.RunWorkerAsync();
}
private void timer_Tick(object sender, EventArgs e) {
if (progressBar.Value < progressBar.Maximum)
progressBar.Increment(5);
else
progressBar.Value = progressBar.Minimum;
}
The Marquee style requires VisualStyles to be enabled, but it continuously scrolls on its own without needing to be updated. I use that for database operations that don't report their progress.
If you can't know the progress you should not fake it by abusing a progress bar, instead just display some sort of busy icon like en.wikipedia.org/wiki/Throbber#Spinning_wheel Show it when starting the task and hide it when it's finished. That would make for a more "honest" GUI.
When you perform operations on Background thread and you want to update UI, you can not call or set anything from background thread. In case of WPF you need Dispatcher.BeginInvoke and in case of WinForms you need Invoke method.
WPF:
// assuming "this" is the window containing your progress bar..
// following code runs in background worker thread...
for(int i=0;i<count;i++)
{
DoSomething();
this.Dispatcher.BeginInvoke((Action)delegate(){
this.progressBar.Value = (int)((100*i)/count);
});
}
WinForms:
// assuming "this" is the window containing your progress bar..
// following code runs in background worker thread...
for(int i=0;i<count;i++)
{
DoSomething();
this.Invoke(delegate(){
this.progressBar.Value = (int)((100*i)/count);
});
}
for WinForms delegate may require some casting or you may need little help there, dont remember the exact syntax now.
The idea behind reporting progress with the background worker is through sending a 'percent completed' event. You are yourself responsible for determining somehow 'how much' work has been completed. Unfortunately this is often the most difficult part.
In your case, the bulk of the work is database-related. There is to my knowledge no way to get progress information from the DB directly. What you can try to do however, is split up the work dynamically. E.g., if you need to read a lot of data, a naive way to implement this could be.
Determine how many rows are to be retrieved (SELECT COUNT(*) FROM ...)
Divide the actual reading in smaller chunks, reporting progress every time one chunk is completed:
for (int i = 0; i < count; i++)
{
bgWorker.ReportProgress((100 * i) / count);
// ... (read data for step i)
}
I have not compiled this as it is meant for a proof of concept. This is how I have implemented a Progress bar for database access in the past. This example shows access to a SQLite database using the System.Data.SQLite module
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
using(SQLiteConnection cnn = new SQLiteConnection("Data Source=MyDatabase.db"))
{
cnn.Open();
int TotalQuerySize = GetQueryCount("Query", cnn); // This needs to be implemented and is not shown in example
using (SQLiteCommand cmd = cnn.CreateCommand())
{
cmd.CommandText = "Query is here";
using(SQLiteDataReader reader = cmd.ExecuteReader())
{
int i = 0;
while(reader.Read())
{
// Access the database data using the reader[]. Each .Read() provides the next Row
if(worker.WorkerReportsProgress) worker.ReportProgress(++i * 100/ TotalQuerySize);
}
}
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Notify someone that the database access is finished. Do stuff to clean up if needed
// This could be a good time to hide, clear or do somthign to the progress bar
}
public void AcessMySQLiteDatabase()
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
This will Helpfull.Easy to implement,100% tested.
for(int i=1;i<linecount;i++)
{
progressBar1.Value = i * progressBar1.Maximum / linecount; //show process bar counts
LabelTotal.Text = i.ToString() + " of " + linecount; //show number of count in lable
int presentage = (i * 100) / linecount;
LabelPresentage.Text = presentage.ToString() + " %"; //show precentage in lable
Application.DoEvents(); keep form active in every loop
}
You have to execute the process from a thread, and from the thread you invoke the progress bar and change its value, maybe this example helps you
public void main()
{
int count = 20;
progressbar.Maximum = count;
progressbar.Value = 0;
new Thread(() => Work(progressbar, count)).Start();
}
public static void Work(ProgressBar progressbar, int count)
{
for (int i = 0; i <= count; i++)
{
Thread.Sleep(70);
// ... do analysis ...
Application.Current.Dispatcher.Invoke(new Action(() =>
{
progressbar.Value = i;
}));
}
}

Categories

Resources