Update progress bar in another form while task is running - c#

**Ultimately I am going to have four tasks running concurrently and have another form that contains four progress bars. I would like for each progress bar to update as it's work task is completing.
Here's what I'm trying to do for starters.
I have a form that has some buttons on it. When I click one I'm creating a new task to do some work.
public partial class MyMainForm : Form
{
private void btn_doWork_Click(object sender, EventArgs e)
{
Task task = new Task(RunComparisons);
task.Start();
}
private void RunComparisons()
{
int progressBarValue = 0;
MyProgressBarForm pBar = new MyProgressBarForm(maxValue, "some text");
pBar.ShowDialog();
foreach(string s in nodeCollection)
{
//do some work here
progressBarValue++;
pBar.updateProgressBar(progressBarValue, "some new text");
}
pBar.BeginInvoke(new Action(() => pBar.Close()));
}
}
In another class that contains a form with a progress bar:
public partial class MyProgressBarForm : Form
{
public MyProgressBarForm(int maxValue, string textToDisplay)
{
InitializeComponent();
MyProgressBarControl.Maximum = maxValue;
myLabel.Text = textToDisplay;
}
public void updateProgressBar(int progress, string updatedTextToDisplay)
{
MyProgressBarForm.BeginInvoke(
new Action(() =>
{
MyProgressBarControl.Value = progress;
myLabel.Text = updatedTextToDisplay;
}));
}
When I click the doWork button the progress bar form displays but doesn't update. It just sits there and hangs. If I comment out the pBar.ShowDialog(); then the progress bar form doesn't display but the work to be done is run to completion perfectly.
I had this working perfectly when I was creating my own threads but I read about Tasks and now I'm trying to get this to run that way. Where did I go wrong?

The TPL adds the IProgress interface for updating the UI with the progress of a long running non-UI operation.
All you need to do is create a Progress instance in your UI with instructions on how to update it with progress, and then pass it to your worker which can report progress through it.
public partial class MyMainForm : System.Windows.Forms.Form
{
private async void btn_doWork_Click(object sender, EventArgs e)
{
MyProgressBarForm progressForm = new MyProgressBarForm();
progressForm.Show();
Progress<string> progress = new Progress<string>();
progress.ProgressChanged += (_, text) =>
progressForm.updateProgressBar(text);
await Task.Run(() => RunComparisons(progress));
progressForm.Close();
}
private void RunComparisons(IProgress<string> progress)
{
foreach (var s in nodeCollection)
{
Process(s);
progress.Report("hello world");
}
}
}
public partial class MyProgressBarForm : System.Windows.Forms.Form
{
public void updateProgressBar(string updatedTextToDisplay)
{
MyProgressBarControl.Value++;
myLabel.Text = updatedTextToDisplay;
}
}
This lets the Progress Form handle displaying progress to the UI, the working code to only handle doing the work, the main form to simply create the progress form, start the work, and close the form when done, and it leaves all of the work of keeping track of progress and marhsaling through the UI thread to Progress. It also avoids having multiple UI thread; your current approach of creating and manipulating UI components from non-UI threads creates a number of problems that complicates the code and makes it harder to maintain.

Create your progress bar form on the main UI thread of the parent form, then call the Show() method on the object in your button click event.
Here's an example with 2 bars:
//In parent form ...
private MyProgressBarForm progressBarForm = new MyProgressBarForm();
private void button1_Click(object sender, EventArgs e)
{
progressBarForm.Show();
Task task = new Task(RunComparisons);
task.Start();
}
private void RunComparisons()
{
for (int i = 1; i < 100; i++)
{
System.Threading.Thread.Sleep(50);
progressBarForm.UpdateProgressBar(1, i);
}
}
//In MyProgressBarForm ...
public void UpdateProgressBar(int index, int value)
{
this.Invoke((MethodInvoker) delegate{
if (index == 1)
{
progressBar1.Value = value;
}
else
{
progressBar2.Value = value;
}
});
}

.ShowDialog is a blocking call; execution won't continue until the dialog returns a result. You should probably look in to a BackgroundWorker to process the work on another thread and update the dialog.

Related

Using threads to run code after ShowDialog()

I'm trying to make a progress bar for my project but while the work is being done they shouldn't be able to interact with the main window (this is why it needs to be ShowDialog). So I've came with this solution but due to having zero multi-threading experience I don't know if this is good solution.
This is just some model code to represent the work that needs to be done and works like it's meant to.
private async void Button_Click(object sender, RoutedEventArgs e)
{
Window1 progress_Bar = new Window1();
Thread test = new Thread(() => test_method(progress_Bar));
test.Start();
for (int i = 0; i < 11; i++)
{
//work
progress_Bar.Bar.Value = i;
await Task.Delay(1000);
}
progress_Bar.Close();
test.Abort();
}
private void test_method(Window1 _Test)
{
_Test.Dispatcher.Invoke(() =>
{
_Test.ShowDialog();
});
}
Where: Window1 is the progress bar window form I'm opening and the user sees while the work is being done and .Bar is the progress bar.
So my main question, is this using threads in a secure a proper way to do asynchronous tasking?
I'd look into the IProgress T interface.
Create a class that will hold your progress information like progress value, max, min if needed..
public class MyProgressReport
{
public int ProgressValue{ get; set; }
}
And a ReportProgress method somewhere in your class which will take progress reports and update info on the UI
private void ReportProgress(MyProgressReport progressReport)
{
//your dialog window needs a property to handle progress
//or just access its Bar.Value like you do in your example
dialogWindow.Progress = progressReport.ProgressValue;
}
And finally a method for showing the dialog / doing work. Tell it what you want to do with an Action.
public async void ShowBusyWindow(Action<IProgress<MyProgressReport>> operation)
{
//ReportProgress method will be called every time you want to update progress
var progressCallback = new Progress<MyProgressReport>(ReportProgress);
progressWindow = new Window();
var workTask = DoWork(operation(progressCallback));
progressWindow.ShowDialog(); //program will wait here until window is closed
await workTask; // usually not needed since workTask will be done by the time this is hit, this is where exceptions will be thrown
}
private async Task DoWork(Action operation)
{
//you should also check for exceptions here and close window
await Task.Run(operation);
//close window when done
progressWindow.Close();
}
Finally, to use it, you would just call Show like so:
ShowBusyWindow((progress) =>
{
//do work here and update progress
var report = new MyProgressReport();
for(int i=0; i<100; i++)
{
report.ProgressValue = i;
progress.Report(report);
Thread.Sleep(500);
}
});
I haven't run this (mistakes may be present) but I use an almost identical approach with some more properties on the progress report class and exception handling.
You need an async ShowDialog method for that
public static class WindowExtensions
{
public static async Task<bool?> ShowDialogAsync(this Window window)
{
await Task.Yield(); // this is the magic ;o)
return window.ShowDialog();
}
}
and now you can do
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click( object sender, RoutedEventArgs e )
{
var dialog = new WaitWindow();
var task = WorkAsync( dialog.Progress );
var dialogTask = dialog.ShowDialogAsync();
await task;
dialog.Close();
await dialogTask;
}
private async Task WorkAsync( IProgress<double> progress )
{
for ( int i = 0; i < 100; i++ )
{
progress.Report( i );
await Task.Delay( 25 ).ConfigureAwait( false );
}
}
}
the WaitWindow class
public partial class WaitWindow : Window
{
public WaitWindow()
{
InitializeComponent();
Progress = new Progress<double>( progress => ProgressHandler( progress ) );
}
public IProgress<double> Progress { get; }
private void ProgressHandler( double progress )
{
progressBar.Value = progress;
}
}

C# Form Controls Won't Update with Multithreading

I've been playing around with multithreading and reading up on some of the questions here, but I haven't found an answer that directly addresses my concerns here.
I have an application that runs on a single thread, except for a progress bar in a separate window. Based on my research, I need to create a new thread for that form which will redraw the form's controls as it's properties change. I've reduced the problem to a simple example below:
Here's the 'main' program:
class Program
{
static MyForm form;
static void Main(string[] args)
{
form = new MyForm();
form.Show();
doWork();
form.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
while (form.Value < 100000)
{
form.ChangeVal();
Thread.Sleep(1);
}
return;
}
}
...And here's the Form. I'm not including the auto-generated stuff from VS.
public partial class MyForm : Form
{
private int val;
public int Value
{
get { return val; }
set { val = value; }
}
public Thread GUIupdater;
public MyForm()
{
InitializeComponent();
this.Refresh();
}
private void MyForm_Load(object sender, EventArgs e)
{
GUIupdater = new Thread(new ThreadStart(GUIupdaterThread));
GUIupdater.Start();
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(killThreadOnClose);
}
public void ChangeVal()
{
val++;
}
private void changeLabel(string s)
{
label.Text = s;
label.Refresh();
}
private delegate void labelChanger(string s);
private void GUIupdaterThread()
{
while (true)
{
Invoke(new labelChanger(changeLabel), new object[]{val.ToString()} );
Thread.Sleep(100); //100 ms
}
}
private void killThreadOnClose(object sender, FormClosingEventArgs e)
{
GUIupdater.Abort();
}
}
So, my intention here is to have the calculations running constantly, with the window's graphics updating reasonably quickly. When I run the program, however, the invoke function is only called once, and the label never actually updates!
Any and all feedback is appreciated. If you want to view the code in an IDE you can download my project from Here
Edits:
When I add Console.WriteLine Statements, I discovered that the GUIupdaterThread (the thing that's meant to update the GUI) loop always 'breaks' on the Invoke statement, never reaching 'Thread.Sleep'. I changed it to 'BeginInvoke', which causes the loop to function properly, but this hasn't changed the fact that the GUI doesn't update.
CLARIFICATIONS:
About my 'actual' project:
The main thread here in 'Program' simulates my software, which is a plugin implementing an interface. My decision to alter val / value in that thread, not in the thread created by the window, was deliberate.
I'm constrained to using .NET 4.0 . any more recent features can't help me
Since in your application you have GUI thread (main thread) - all UI controls will be accessible from this thread only.
There are several approaches how to update controls from other threads.
I would like to recommend you to use one of modern and native approaches based on Progress < T > class (it's native for .Net platform).
I would suggest overriding the form's OnPaint method. Then inside ChangeVal, after you have updated whatever variables/data you need to update, call this.Invalidate which should trigger the form to repaint itself.
Or if you're just updating a single label, call label.Refresh in your ChangeVal method. The form should update correctly. Here's an example that worked for me:
This form has a single label on it.
public partial class ProgressForm : Form
{
private int currentValue = 0;
public ProgressForm()
{
InitializeComponent();
}
public void ChangeValue(int newValue)
{
currentValue = newValue;
lblValue.Text = string.Format("Current value: {0}", currentValue);
lblValue.Refresh(); //Call Refresh to make the label update itself
}
}
static class Program
{
private static ProgressForm progressForm = null;
[STAThread]
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
progressForm = new ProgressForm();
progressForm.Show();
doWork();
progressForm.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
int i = 0;
while (i < 100000)
{
progressForm.ChangeValue(i);
Thread.Sleep(1);
i++;
}
return;
}
}
You may use the following instead as you are trying to access UI control other than main thread (from which it is created).
while ( true )
{
Invoke ( ( Action ) (() =>
{
label.Text = val.ToString();
label.Refresh()
Application.DoEvents();
}));
Thread.Sleep( 100 );
}
I recommend you to use "backgroundworker".
First add CheckForIllegalCrossThreadCalls = false; to initialization part otherwise InvalidOperationException occurs.
private void btnDoIt_Click(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Foo();
}
int total = 0;
private void Foo()
{
for (int i = 0; i <= 100000; i++)
{
total += i;
this.Text = i.ToString();
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run next process
}

Thread updating GUI freezes in random moments

I have form with button and text box. Button is starting thread which is updating value of text box.
public Form1()
{
InitializeComponent();
myDelegate = new UpdateUi(updateUi);
}
private void button1_Click(object sender, EventArgs e)
{
myThread = new Thread(new ThreadStart(ThreadFunction));
myThread.Start();
}
private void ThreadFunction()
{
MyThreadClass myThreadClassObject = new MyThreadClass(this);
myThreadClassObject.Run();
}
private void updateUi(int i)
{
textBox1.Text = i.ToString();
Thread.Sleep(1000);
}
public Thread myThread;
public delegate void UpdateUi(int i);
public UpdateUi myDelegate;
and ThreadClass:
public class MyThreadClass
{
Form1 myFormControl1;
public MyThreadClass(Form1 myForm)
{
myFormControl1 = myForm;
}
public void Run()
{
// Execute the specified delegate on the thread that owns
// 'myFormControl1' control's underlying window handle.
for(int i=0;i<100;i++)
{
if(myFormControl1.InvokeRequired)
{
myFormControl1.Invoke(myFormControl1.myDelegate,i);
}
}
}
}
As You can see there is nothing special in my code but sometimes the code freeze.
eg it goes 1->2->3->freeze->16->17 and so on.
I took code from HERE with little modifications
The issue is you are delaying the UI thread not the the process itself so what happens is you issue all the update commands but since it all runs on the same thread it gets clogged because the Thread.Sleep stops the UI thread so it runs a bunch of textBox1.Text = i.ToString(); then it stops for all the time of all the Thread.Sleep(1000); probably the number of 1->2->3... you see is equal to the number of cores in your machine.
When you stop the run method what happens is you issue one update command that runs immediately and wait for one second until you issue the next command witch I think its what you are trying to accomplish.

Show form in main thread from another thread

I developing multithreading application with main form and another form in which progress is shown.
At first: I create ProgressForm in MainForm
Progress p=new Progress();
Second: I create new instance of class Model (whith all data in my app).
Model m = new Model();
And subscribe for event:
m.OperationStarted += new EventHandler(OnCopyStarted);
private void OnCopyStarted(object sender, EventArgs e)
{
p.Show();
}
Third: I run some operation in another thread where I change property in another Model
private bool isStarted;
public bool IsStarted
{
get{return isStarted;}
set
{
isStarted = value;
if (isStarted && OperationStarted != null)
{
OperationStarted(this, EventArgs.Empty);
}
}
}
My questoin is: Why Progress form is show not in Main Thread? How can I run it without lockups?
All UI operations must run on the main UI thread.
The OnCopyStarted method is being called on another thread, so it must switch to the UI thread before before showing the dialog.
You can use your form's BeginInvoke to switch to the UI thread. Such as:
void OnCopyStarted(object sender, EventArgs e)
{
p.BeginInvoke((Action) (() => p.Show()));
}
Try it :
var t = new Thread(() => {
Application.Run(new Progress ());
});
t.Start();

Thread safe form manipulation between two forms (WinForms C#)

I have two forms, the main form and one that pops up as a modal dialog. From a process spawned in the main form, I want to dynamically update the text on the modal dialog. Here's what I have:
In the main form, I do this:
// show the wait modal
var modal = new WaitDialog { Owner = this };
// thread the packaging
var thread = new Thread(() => Packager.PackageUpdates(clients, version, modal));
thread.Start();
// hopefully it worked ...
if (modal.ShowDialog() != DialogResult.OK)
{
throw new Exception("Something failed, miserably.");
}
The PackageUpdates method takes the modal dialog, and does this:
// quick update and sleep for a sec ...
modal.SetWaitLabelText("Downloading update package...");
Thread.Sleep(2000);
modal.SetWaitLabelText("Re-packaging update...");
To be thread safe, I do this in the modal dialog:
public void SetWaitLabelText(string text)
{
if (lblWaitMessage.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
lblWaitMessage.Text = text;
}
}
Everything works great ... most of the time. Every three or four times that the modal pops up, I get an exception on the lblWaitMessage.Text = text; and it's not invoking the command.
Am I missing something in this setup?
Like #Hans Passant pointed out, you should wait for the modal.Load-event. One good option is to use the ManualResetEvent to inform your thread to wait until that happens.
The WaitOne method will block the thread until the Set method is called. Here's a very simple setup which should do the trick.
public partial class Form1 : Form
{
ManualResetEvent m_ResetEvent;
public Form1()
{
InitializeComponent();
m_ResetEvent = new ManualResetEvent(false);
}
private void button1_Click(object sender, EventArgs e)
{
Dialog d = new Dialog { Owner = this, ResetEvent = m_ResetEvent };
var thread = new Thread(new ParameterizedThreadStart(DoSomething));
thread.Start(d);
if (d.ShowDialog() != System.Windows.Forms.DialogResult.OK)
{
throw new Exception("Something terrible happened");
}
}
private void DoSomething(object modal)
{
Dialog d = (Dialog)modal;
// Block the thread!
m_ResetEvent.WaitOne();
for (int i = 0; i < 1000; i++)
{
d.SetWaitLabelText(i.ToString());
Thread.Sleep(1000);
}
}
}
And here is the modal form
public partial class Dialog : Form
{
public Form Owner { get; set; }
public ManualResetEvent ResetEvent { get; set; }
public Dialog()
{
InitializeComponent();
}
public void SetWaitLabelText(string text)
{
if (label1.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
label1.Text = text;
}
}
private void Dialog_Load(object sender, EventArgs e)
{
// Set the event, thus unblocking the other thread
ResetEvent.Set();
}
}
I think you should rewrite the code to let thread.Start() isn't called before modal.ShowDialog().
As a workaround, you can try this:
public void SetWaitLabelText(string text) {
Invoke(new Action<string>(SetWaitLabelText2), text);
}
void SetWaitLabelText2(string text) {
lblWaitMessage.Text = text;
}
The first method always uses Invoke, regardless the value of InvokeRequired. The second method actually does the thing. This pattern is usable when you always call the function from another thread.

Categories