Multi-threading is a concept I am attempting to understand. I Am simply trying to create a "Working Please Wait" window with a Text message update as to the progress.
I cannot seem to Send a Message back to that thread to update the text message.
Here is how I am Starting my Thread:
frmProcessing Fp;
Thread FpThread = new Thread(() => {
Fp= new frmProcessing();
Fp.Tital("Building Database");
Fp.Working("Clearing Old Data...");
Fp.ShowDialog();
});
FpThread.SetApartmentState(ApartmentState.STA);
FpThread.Start();
Attempting to run this function from the Thread preforming the updating action(original thread):
Fp.Working("new message");
I attempted this:
Fp.Invoke(new Action(() => {
Fp.Working("new message");
}));
I get this error:
'Fp' is not null here.
CS0165:Use of unassigned local variable 'fp'
Anyone Finding this Post.
What fixed my problem was 3 parts.
setting the NewForm to Null.
frmProcessing Fp;
now
frmProcessing Fp=null;
using BeginInvoke() functions on the Form running in the separate thread. Not from the function trying to access the form.
public void Working(string Working) // function on form running in new thread
{
BeginInvoke(new Action(() => lblProcess.Text = Working));
}
More Code can be added to test to see if the message is coming from another thread, to be more complete.
Access to the methods and functions of the form in the new thread takes some time to start, so in my case I delayed accessing the forms methods for about 1 second by
threading.sleep(1000);
There might be Better ways to accomplish this task. But it works for me.
Based on our conversation, here's one of many ways achieve that outcome. (In other words, this is how I personally do this kind of thing.)
public partial class MainForm : Form
{
private void buttonModal_Click(object sender, EventArgs e)
{
var modal = new Progress();
// Start a worker task to perform the
// SQL updates and reset the application.
Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
// Block on the thread doing the updating.
// What you do here is up to you. This just
// simulates work that takes some amount of time.
Task.Delay(25).Wait();
modal.SetProgress(i);
}
Invoke((MethodInvoker)delegate{ modal.Dispose(); });
});
// The modal dialog will prevent user interaction,
// effectively locking user out until the update completes.
modal.ShowDialog();
}
}
where...
public partial class Progress : Form
{
public void SetProgress(int i)
{
if (IsHandleCreated) // Avoid potential race condition
{
Invoke((MethodInvoker)delegate { progressBar1.Value = i; });
}
}
// This is probably in Progress.Designer.cs file
protected override void Dispose(bool disposing)
{
if (disposing)
{
// A debug message to confirm that this dialog is getting disposed.
Debug.WriteLine("Progress dialog is now Disposing");
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
}
Related
I have an application that connects to a REST API using async methods. I have this set up using async/await pretty much everywhere that connects to the API, however I have a question and some strange behavior that I don't completely understand. What I want to do is simply return a license in certain scenarios when the program shuts down. This is initiated by a window closing event; the event handler is as follows:
async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
...other synchronous code...
//Check for floating licensing
if (KMApplication.License != null && KMApplication.License.Scope != Enums.LicenseScope.Standalone)
{
for (int i = 0; i < 3; i++)
{
try
{
await KMApplication.License.ShutDown(KMApplication.Settings == null
? Enums.LicenseReturnModes.PromptOnShutdown
: KMApplication.Settings.LicenseReturnMode)
.ConfigureAwait(false);
break;
}
catch (Exception ex)
{
_logger.Warn("Exception in license release, attempt " + i, ex);
}
}
}
await KMApplication.ApiService.Disconnect().ConfigureAwait(false);
_logger.Info("Shutdown Complete");
Application.Current?.Shutdown();
}
When this runs I can step through in the debugger and it gets to the first license shutdown call which is the first async awaited call. Then when I press F10 to step to the next line of code it just shuts down and is gone. I verified that the license release that is supposed to be happening in that line is in face happening so it appears to run to completion of that line but then shuts down or crashes or something. I also looked at the logs and it never gets to the Shutdown Complete line and I don't believe it's getting to the ApiService.Disconnect either.
I also tried running this as a sync method using Task.Run(() => ...the method...).GetAwaiter().GetResult() but that just deadlocks on the first call.
How do I handle this and have it run the async release, wait for it to be done, then shut down?
The fundamental problem in what you're trying to do is that async/await assumes the main application thread continues running. This assumption directly conflicts with the shutdown action, whose job is to terminate all running tasks.
If you examine the documentation on Window_Closing, it states the following (and only the following):
Occurs directly after Close() is called, and can be handled to cancel window closure.
This is important. The only thing this is supposed to do is allow you to programmatically cancel the window closure, thus prompting some additional user action.
Your expectations are befuddled because of how async/await works. Async/await appears to run in a linear fashion; however, what actually happens is that control is passed back to the caller at the first await. The framework assumes at that point that you do not wish to cancel the form close, and the program is allowed to terminate, taking all other actions with it.
Fundamentally, all C-style programs have a main entry point, which runs a loop. It's been that way since the early days of C, and continues that way in WPF. However, in WPF, Microsoft got a bit clever, and decided to hide this from the programmer. There are a couple of options to deal with things that need to happen after main window closing:
Re-hijack the main loop from your program, and put the code there. The details on how to do this may be found here.
Set an explicit shutdown mode, and kick off the task to initiate that. Call Application.Shutdown() as the very last line of code you need to execute.
Here is an async version of the FormClosing event. It delays the closing of the form until the completion of the supplied Task. The user is prevented from closing the form before the completion of the task.
The OnFormClosingAsync event passes an enhanced version of the FormClosingEventArgs class to the handling code, with two additional properties: bool HideForm and int Timeout. These properties are read/write, much like the existing Cancel property. Setting HideForm to true has the effect of hiding the form while the async operation is in progress, to avoid frustrating the user. Setting Timeout to a value > 0 has the effect of abandoning the async operation after the specified duration in msec, and closing the form. Otherwise it is possible that the application could be left running indefinitely with a hidden UI, which could certainly be a problem. The Cancel property is still usable, and can be set to true by the handler of the event, to prevent the form from closing.
static class WindowsFormsAsyncExtensions
{
public static IDisposable OnFormClosingAsync(this Form form,
Func<object, FormClosingAsyncEventArgs, Task> handler)
{
Task compositeTask = null;
form.FormClosing += OnFormClosing; // Subscribe to the event
return new Disposer(() => form.FormClosing -= OnFormClosing);
async void OnFormClosing(object sender, FormClosingEventArgs e)
{
if (compositeTask != null)
{
// Prevent the form from closing before the task is completed
if (!compositeTask.IsCompleted) { e.Cancel = true; return; }
// In case of success allow the form to close
if (compositeTask.Status == TaskStatus.RanToCompletion) return;
// Otherwise retry calling the handler
}
e.Cancel = true; // Cancel the normal closing of the form
var asyncArgs = new FormClosingAsyncEventArgs(e.CloseReason);
var handlerTask = await Task.Factory.StartNew(
() => handler(sender, asyncArgs),
CancellationToken.None, TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default); // Start in a thread-pool thread
var hideForm = asyncArgs.HideForm;
var timeout = asyncArgs.Timeout;
if (hideForm) form.Visible = false;
compositeTask = Task.WhenAny(handlerTask, Task.Delay(timeout)).Unwrap();
try
{
await compositeTask; // Await and then continue in the UI thread
}
catch (OperationCanceledException) // Treat this as Cancel = true
{
if (hideForm) form.Visible = true;
return;
}
catch // On error don't leave the form hidden
{
if (hideForm) form.Visible = true;
throw;
}
if (asyncArgs.Cancel) // The caller requested to cancel the form close
{
compositeTask = null; // Forget the completed task
if (hideForm) form.Visible = true;
return;
}
await Task.Yield(); // Ensure that form.Close will run asynchronously
form.Close(); // Finally close the form
}
}
private struct Disposer : IDisposable
{
private readonly Action _action;
public Disposer(Action disposeAction) => _action = disposeAction;
void IDisposable.Dispose() => _action?.Invoke();
}
}
public class FormClosingAsyncEventArgs : EventArgs
{
public CloseReason CloseReason { get; }
private volatile bool _cancel;
public bool Cancel { get => _cancel; set => _cancel = value; }
private volatile bool _hideForm;
public bool HideForm { get => _hideForm; set => _hideForm = value; }
private volatile int _timeout;
public int Timeout { get => _timeout; set => _timeout = value; }
public FormClosingAsyncEventArgs(CloseReason closeReason) : base()
{
this.CloseReason = closeReason;
this.Timeout = System.Threading.Timeout.Infinite;
}
}
Since OnFormClosingAsync is an extension method and not a real event, it can only have a single handler.
Usage example:
public Form1()
{
InitializeComponent();
this.OnFormClosingAsync(Window_FormClosingAsync);
}
async Task Window_FormClosingAsync(object sender, FormClosingAsyncEventArgs e)
{
e.HideForm = true; // Optional
e.Timeout = 5000; // Optional
await KMApplication.License.ShutDown();
//e.Cancel = true; // Optional
}
The Window_FormClosingAsync handler will run in a thread-pool thread, so it should not include any UI manipulation code.
Unsubscribing from the event is possible, by keeping a reference of the IDisposable return value, and disposing it.
Update: After reading this answer, I realized that it is possible to add a real event FormClosingAsync in the form, without creating a class that inherits from the form. This can be achieved by adding the event, and then running an initialization method that hooks the event to the native FormClosing event. Something like this:
public event Func<object, FormClosingAsyncEventArgs, Task> FormClosingAsync;
public Form1()
{
InitializeComponent();
this.InitFormClosingAsync(() => FormClosingAsync);
this.FormClosingAsync += Window_FormClosingAsync_A;
this.FormClosingAsync += Window_FormClosingAsync_B;
}
Inside the initializer, in the internal handler of the native FormClosing event, all the subscribers of the event can be retrieved
using the GetInvocationList method:
var eventDelegate = handlerGetter();
if (eventDelegate == null) return;
var invocationList = eventDelegate.GetInvocationList()
.Cast<Func<object, FormClosingAsyncEventArgs, Task>>().ToArray();
...and then invoked appropriately. All this adds complexity, while the usefulness of allowing multiple handlers is debated. So I would probably stick with the original single-handler design.
Update: It is still possible to have multiple handlers using the original method OnFormClosingAsync. It is quite easy actually. The Func<T>
class inherits from Delegate, so it has invocation list like a real event:
Func<object, FormClosingAsyncEventArgs, Task> aggregator = null;
aggregator += Window_FormClosingAsync_A;
aggregator += Window_FormClosingAsync_B;
this.OnFormClosingAsync(aggregator);
No modification in the OnFormClosingAsync method is required.
Ok here is what I ended up doing. Basically the window closing kicks off a task that will wait for the release to happen and then invoke the shutdown. This is what I was trying to do before but it didn't seem to work in async void method but it seems to be when done this way. Here is the new handler:
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
...other sync code...
Task.Run(async () =>
{
await InvokeKmShutdown();
(Dispatcher ?? Dispatcher.CurrentDispatcher).InvokeShutdown();
});
}
And the shutdown method looks like this:
async Task InvokeKmShutdown()
{
...other sync code...
await KMApplication.ApiService.Disconnect();
//Check for floating licensing
if (KMApplication.License != null && KMApplication.License.Scope != License.Core.Enums.LicenseScope.Standalone)
{
for (int i = 0; i < 3; i++)
{
try
{
await KMApplication.License.ShutDown(KMApplication.Settings == null
? Enums.LicenseReturnModes.PromptOnShutdown
: KMApplication.Settings.LicenseReturnMode);
break;
}
catch (Exception ex)
{
_logger.Warn("Exception in license release, attempt " + i, ex);
}
}
}
}
Hope it helps someone.
EDIT
Note that this is with an WPF app set to ShutdownMode="OnExplicitShutdown" in App.xaml so it won't shut down the actual app until I call the shutdown. If you are using WinForms or WPF is set to shut down on last window or main window close (main window close is the default I believe) you will end up with the race condition described in the comments below and may get the threads shut down before things run to completion.
I have a console application where in some instances a user interface needs to be presented. This user interface needs to remain responsive as it will contain a loading gif, progress bar, cancel button etc. I have the following sample code:
class Program
{
static void Main(string[] args)
{
DoWork().GetAwaiter().GetResult();
}
private static async Task DoWork()
{
TestForm form = new TestForm();
form.Show();
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form.Close();
}
}
}
I would expect from the code above for the TestForm to be displayed for approximately 5 seconds before being closed due to the value of the string being "Plop", however all that happens is the Task is run and the if statement is never reached. Furthermore the UI of the TestForm does not remain responsive. What is wrong with this code?
So I've managed to hack together a dirty solution for this. It is not a clean solution so I'm still open to suggestions but for what I need it works fine
private static void DoWork()
{
TestForm form = new TestForm();
Task formTask = Task.Run(() => form.ShowDialog());
Task<string> testTask = Task.Run(() =>
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine(i.ToString());
}
Console.WriteLine("Background task finished");
return "Plop";
});
Console.WriteLine("Waiting for background task");
testTask.Wait();
if (testTask.Result == "Plop")
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
}
Console.WriteLine("App finished");
}
This outputs 'Waiting for background task' first, followed by the number count of the Task and then outputs 'Background task finished' when the long process is complete, as well as closes the responsive UI form
Its a classic deadlock.When your code hit await ,control goes back to main thread which is a blocking wait for DoWork GetResult(); When Task.Run thread is finished controls tries to go back to main thread but its waiting for DoWork to be finished. That is the reason last If statement never executes.
But apart from deadlock ,there is also one more issue in your code which will make your UI freeze.Its the form.Show() method.If you remove everything related to async-await and only use form ,it will still freeze.The problem is Show method expects a windows message loop which will be provided if you create a Windows.Forms application but here you are launching form from console application which doesnt have a message loop. One solution would be to use form.ShowDialog which will create its own message loop. Another solution is to use System.Windows.Forms.Application.Run method which provides a win messages loop to the form created through thread pool thread. I can give you one possible solution here but its up to you how you structure your code as the root cause is identified.
static void Main(string[] args)
{
TestForm form = new TestForm();
form.Load += Form_Load;
Application.Run(form);
}
private static async void Form_Load(object sender, EventArgs e)
{
var form = sender as Form;
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form?.Close();
}
}
Ok I did mark my first answer to be deleted, since what I put there works for WPF and not for you require, BUT in this one is doing what you asked, I did try it and opens the WinForm then closes after 5 seconds, here is the code:
static void Main(string[] args)
{
MethodToRun();
}
private static async void MethodToRun()
{
var windowToOpen = new TestForm();
var stringValue = String.Empty;
Task.Run(new Action(() =>
{
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
windowToOpen.Show();
}).Wait();
System.Threading.Thread.Sleep(5000);
stringValue = "Plop";
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
if (String.Equals(stringValue, "Plop"))
{
windowToOpen.Close();
}
}).Wait();
})).Wait();
}
I'm trying to use a background worker to update a listbox used for a status window in my Form in C#. It doesn't appear to work properly when the addToStausLog() method is called from another class outside of the MyForm class even though I pass an instance of the form to the other class that's calling the addToStatusLog update member. Instead the update doesn't happen until the class member finished and returns back to the MyForm class. Maybe there's a better a approach to creating real-time status windows that will run from any class that MyForm is passed into. I'm new to worker threads, so could someone review and let me know what I might be doing wrong or could improve on.
public MyForm()
{
InitializeComponent();
// Setup background task to update listbox status so UI is unaffected
_lListBoxQue = new List<string>();
bw_listBoxBGWorker = new BackgroundWorker();
bw_listBoxBGWorker.DoWork += (o, args) => LstbxThread_doWork();
bw_listBoxBGWorker.RunWorkerCompleted += (o, args) => LstbxThread_completed();
}
private void LstbxThread_doWork()
{
System.Threading.Thread.Sleep(100);
}
private void LstbxThread_completed()
{
// Update listbox
lstStatusBox.BeginUpdate();
lstStatusBox.Items.Clear(); // clear entries
lstStatusBox.Items.AddRange(_lListBoxQue.ToArray());
lstStatusBox.EndUpdate();
}
public String addToStatusLog(String sMsg)
{
_lListBoxQue.Add(sMsg);
if (_lListBoxQue.Count > _iStatusLogMaxLines) // > max?
_lListBoxQue.RemoveAt(0); // remove top element?
if( !bw_listBoxBGWorker.IsBusy ) // background not busy?
bw_listBoxBGWorker.RunWorkerAsync(); // update listbox in back ground task
System.Threading.Thread.Sleep(100);
return sMsg;
}
This is the member that calls another class which attempts to call the addToStatusLog several times during the process, but the updates to the listbox don't happen until the MyClass(this).updateDB() finishes. I need to see real-time updates as the updateDB() function is running. There has to be a way to make this work, I'm hoping...
private void btnUpdateDB_Click(object sender, EventArgs e)
{
if (_bIsUpdateEventRunning == false ) // is event not busy?
{
_bIsUpdateEventRunning = true;
new MyClass(this).updateDB();
_bIsUpdateEventRunning = false;
}
}
Example of class called to update the form listbox.
Public class MyClass{
private MyForm _pForm;
public MyClass(MyForm pForm){ _pForm= pForm; }
public void updateDB(){
_pForm.addToStatusLog("Hello World");
}
}
Updated Fix w/o background worker:
public String addToStatusLog(String sMsg)
{
_lListBoxQue.Add(sMsg);
if (_lListBoxQue.Count > _iStatusLogMaxLines) // > max?
_lListBoxQue.RemoveAt(0); // remove top element?
lstStatusBox.BeginUpdate();
lstStatusBox.Items.Clear(); // clear entries
lstStatusBox.Items.AddRange(_lListBoxQue.ToArray());
lstStatusBox.EndUpdate();
Application.DoEvents();
return sMsg;
}
Thread.Sleep is not the answer here. What you likely need is Application.DoEvents. This processes all messages currently waiting in the Windows message queue.
Thread.Sleep just tells the thread to go to sleep for the number of milliseconds you specify. If your background worker is running on the UI thread, you're putting the UI thread to sleep and it's effectively comatose. (Important: All Windows forms run on the UI thread.)
There are, of course, alternative designs that involve spinning up separate threads of execution. But these have their own issues, and you should be mindful of them before running blindly down that path.
Background
Currently working on a windows form app which I asked to create. I have ran into an issue where the UI freezes when a resource intensive process is being called. I am currently using threading from which I understand is used to prevent the UI from freezing and taking over the entire pc.
Question
Currently when I am using threading to call a method in my base class which is to open a file that is located on a remote server. This method has a delay of approximately 30 to 45 seconds. I am creating my background thread and invoking it to start. When invoked to start if fires, however when it fired it would not wait for my thread to complete basically giving me a null exception. So after some digging I found that in order to wait for the thread to complete you had to invoke the .Join(). However when the Join is invoked it froze my UI completely. So my ingenuity tried to create a work around and created a while loop that would until the thread is no longer alive and continue. However, this also froze the UI. So am I missing something? That is not mention in MSDN Doc
Code Sample
class BaseClass
{
public CWClient ClientFileContext(string clientFile, bool compress)
{
Client clientContext = null;
try
{
if (compress == true)
{
clientContext = appInstance.Clients.Open2(clientFile, superUser, passWord, OpenFlags.ofCompressed);
}
else
{
clientContext = appInstance.Clients.Open2(clientFile, superUser, passWord, OpenFlags.ofNone);
}
}
catch (Exception ex)
{
//TODO
}
return clientContext;
}
}
public partial class Form1 : Form
{
private void button1_Click(object sender, EventArgs e)
{
BaseClass wpSec = new BaseClass();
CWClient client = null;
Thread backgroundThread = new Thread(
new ThreadStart(() =>
{
client = wpSec.ClientFileContext(selectedFileFullPath, true);
}
));
backgroundThread.Start();
//backgroundThread.Join(); << Freezes the UI
var whyAreYouNotWorking = "Stop";
}
}
Work around I tried
while (backgroundThread.IsAlive == true)
{
for (int n = 0; n < 100; n++)
{
Thread.Sleep(500);
progressBar1.BeginInvoke(new Action(() => progressBar1.Value = n));
}
}
// This also freezes the UI
I would also look into the async and await pattern for this. Explained in this post: Using async await still freezes GUI
Your code should be similar to this (Baseclass doesn't change) :
public partial class Form1 : Form
{
private async void button1_Click(object sender, EventArgs e)
{
BaseClass wpSec = new BaseClass();
CWClient client = await Task.Run(() =>
{
return wpSec.ClientFileContext(selectedFileFullPath, true);
}
);
var whyAreYouNotWorking = "Stop";
}
}
This is back-of-the-envelope stuff, but hopefully that gives the basic idea of launching a task, then awaiting the result in an async method. If you don't need your BaseClass hanging around, that can be in the lambda too, leaving you only what you really want.
That link from #Chris Dunaway above is also excellent. http://blog.stephencleary.com/2013/08/taskrun-vs-backgroundworker-round-3.html
Edit: As #BradlyUffner mentions, this is also one of the few times you should use async void and should rather prefer returning Task or Task<T> in virtually all other circumstances.
I know there are a lot of similar questions out there and I have read through a lot of them. Unfortunately, I still couldn't solve my problem after reading through them - however I am relatively new to C#. According to docs the problem of not being thread safe results in an InvalidOperationException when using the debugger. This is not the case in my problem.
I've recreated the problem with a simple raw test class to concentrate on my problem.
The main form is supposed to show a kind of a progress dialog.
public partial class ImportStatusDialog : Form
{
public ImportStatusDialog()
{
InitializeComponent();
}
public void updateFileStatus(string path)
{
t_filename.Text = path;
}
public void updatePrintStatus()
{
t_printed.Text = "sent to printer";
}
public void updateImportStatus(string clientName)
{
t_client.Text = clientName;
}
public void updateArchiveStatus()
{
t_archived.Text = "archived";
}
}
When that code is called without any Invoke() from the main form:
private void button1_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
nDialog.updateFileStatus("test");
Thread.Sleep(1000);
nDialog.updateImportStatus("TestClient");
Thread.Sleep(1000);
nDialog.updatePrintStatus();
Thread.Sleep(1000);
nDialog.updateArchiveStatus();
Thread.Sleep(1000);
nDialog.Close();
}
And even when I call it like this:
private void button3_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateFileStatus("Test");
});
}
else
{
nDialog.updateFileStatus("Test");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updatePrintStatus();
});
}
else
{
nDialog.updatePrintStatus();
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateImportStatus("cName");
});
}
else
{
nDialog.updateImportStatus("cName");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateArchiveStatus();
});
}
else
{
nDialog.updateArchiveStatus();
}
Thread.Sleep(1000);
nDialog.Close();
}
the dialog which looks like this in the designer (in my example)
will be displayed like that:
When I use ShowDialog() instead of Show() the dialog displays correnctly, but as the API Doc points out
You can use this method to display a modal dialog box in your
application. When this method is called, the code following it is not
executed until after the dialog box is closed
which is not what I want, especially as it would mean that the dialog updating would only happen after it has been closed again.
What am I doing wrong here? This seems to be a trivial problem and yet the solution evades me. Please keep in mind that I am new to C# GUI programming.
Also, I would like to ask what would be the right place for using the Invoke()? Do Would you use it in the main form when calling the dialog methods or rather in the dialog update methods itself?
It doesn't look like you're using multiple threads here, so the invoke stuff is not required. Invoke is only needed for cross-thread calls - you are creating multiple forms, but all your code is running in the same thread.
If you're doing your work in the main UI thread (as implied by the code being in a button click event), then just call Application.DoEvents() periodically to allow the progress form to refresh.
A better solution would be to use a BackgroundWorker for your work, and have it report its progress periodically.
Then you can update the progress form in the BackgroundWorker's ProgressChanged event (which will be executed in the main thread, so you still don't have to invoke anything).