Progress bar (spinner) not animated when background thread is working - c#

I have a background worker that does some work. I want to have a spinner on main control indicating that app is working. But looks like threading is preventing my spinner from animating (sometimes it doesnt even show)... can some one explain why it is not working (probably because sleeping the thread) and perhaps guide me to a solution with minimal code changes :)
Best regards, no9.
public void StartProcess(object obj)
{
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Info)
{
Title = "Start",
Description = "Starting action..."
});
Worker = new BackgroundWorker();
Worker.DoWork += new DoWorkEventHandler(worker_DoWork);
Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
try
{
this.Document = null;
Dictionary<string, Stream> tmp = this.GetContent();
//start and show the spinner
this.View.ShowDocumentProgressSpinner(true);
Worker.RunWorkerAsync(tmp);
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, "LogAndSwallow");
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Error)
{
Title = "Error",
Description = "There was an error processing your action."
});
}
finally
{
this.View.ShowActionButton(false);
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var logger = new ActionLoggerAndViewUpdater(this.eventAggregator, this.View);
foreach (KeyValuePair<string, Stream> pair in (Dictionary<string, Stream>)e.Argument)
{
using (Stream stream = pair.Value)
{
//setting the document fires login event that changes stuff on presenter (current class instance)
this.Document = new Document(stream);
//check if ok to continue
while (!this.IsLoggedInForTheWorkingDocument)
//wait of the login stuff to complete
Thread.Sleep(2000);
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//this depends on the login and takes some time to process
this.DoSomeStuff();
}));
}
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//stop and hide the spinner
this.View.ShowDocumentProgressSpinner(false);
...
}

Why do you invoke something on your background worker? The background workers whole purpose is to not run in the UI thread and block it. Do not invoke a long running process from the worker, it defeats it's purpose.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var logger = new ActionLoggerAndViewUpdater(this.eventAggregator, this.View);
foreach (KeyValuePair<string, Stream> pair in (Dictionary<string, Stream>)e.Argument)
{
using (Stream stream = pair.Value)
{
this.Document = new Document(stream);
// this should REALLY be handled by an event, not busy waiting:
while (!this.IsLoggedInForTheWorkingDocument)
//wait of the login stuff to complete
Thread.Sleep(2000);
// removed the invoking, this is supposed to run in the background, right?
this.DoSomeStuff();
}
}
}

Because your worker runs in another thread, it cannot update the UI thread as it progresses, hence the behaviour. See this other, similar question Updating GUI (WPF) using a different thread.
Essentially you need to call control.Dispatcher.Invoke to get your UI to update from the other thread

Related

Trying to call Invoke method throws an exception in a winform app

I have a legacy project (Visual Studio 2003) which was made in the past by other people and now I need to maintain it. This legacy app communicate with another one using Windows Message Queues. One queue for receive, queueInput, and another one for send, queueOutput.
There is an action which is divided in six steps. While the action is being performed the progress is shown in the winform, let's say MyFrm.
So the action is started by instantiating a winform and then from its load event, first step is launched through a thread:
MyFrm frm = new MyFrm(param1, param2);
frm.ShowDialog();
Load event:
private void MyFrm_Load(object sender, System.EventArgs e)
{
Thread th = new Thread(new ThreadStart(BeginFirstStep));
th.Start();
}
Then some threads are created and executed in cascade:
private void BeginFirstStep()
{
// Do some stuff
Invoke(new ThreadStart(FirstStepCompleted));
}
private void FirstStepCompleted()
{
myLabel1.Visible = true;
// Do some other stuff
Thread th = new Thread(new ThreadStart(BeginSecondStep));
th.Start();
}
private void BeginSecondStep()
{
// Do some stuff
Invoke(new ThreadStart(SecondStepCompleted));
}
private void SecondStepCompleted()
{
label3.enabled = false;
// Do some other stuff
if (some_condition)
{
ThirdSteepCompleted();
return;
}
else if (some_other_condition)
{
Thread th = new Thread(new ThreadStart(Do_AnotherTask));
th.Start();
}
else
{
Do_AnotherTask2();
}
}
private MessageQueue queueInput;
private MessageQueue queueOutput;
private void Do_AnotherTask2(object state)
{
if(InvokeRequired)
{
Invoke(new TimerCallback(Do_AnotherTask2), new object[]{state});
}
else
{
queueOutput = new MessageQueue(#".\Private$\Actions");
queueInput = MessageQueue.Create(#".\Private$\Response");
queueInput.ReceiveCompleted += new ReceiveCompletedEventHandler(queueInput_ReceiveCompleted);
queueInput.BeginReceive();
queueOutput.Send(BuildMsgToSend());
}
}
From another winform app, the response is added to this queue and then the event
queueInput_ReceiveCompleted is raised:
private void queueInput_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
{
// Do some stuff with data received
Invoke(new ThreadStart(ThirdStepCompleted)); // HERE IT SOMETIMES CRASHES (NOT ALWAYS).
}
private void ThirdStepCompleted()
{
// Do some other stuff
Thread th = new Thread(new ThreadStart(BeginFourthStep));
th.Start();
}
and so on... with the rest of the steps.
The problem is:
Sometimes (not always), program crashes at queueInput_ReceiveCompleted handler method when performing the Invoke. The error says:
System.InvalidOperationException: Cannot call Invoke or InvokeAsync on
a control until the window handle has been created.
I know, it is a complex and difficult code to understand, but it was designed as is....

When I use cross thread then my application doesn't respond until background operations complete

I'm working on windows from application in .net framework 2.0.
There is some operations run in background like database backup, progress bar and label text update etc.
But When I use cross thread then my application doesn't respond(busy icon) until background operations complete
This is example code
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(UpdateInfo));
t.Start();
}
private void UpdateInfo()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(UpdateInfo));
}
else
{
// send query to database here for taking backup that could take time
// update progress bar
//I'm also using sqlconnection InfoMessage here
label1.Text = "Text upading......
}
}
private void OnInfoMessage(sender As Object, e As SqlInfoMessageEventArgs)
{
}
Scenario:
Scenario is user could cancel operation but it can't due to application not responding
================Update Code==========================================
My Code is like
private void btnBackup_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(MyThreadFunc));
t.Start();
}
public void MyThreadFunc()
{
if (this.InvokeRequired) {
this.Invoke(new MethodInvoker(Backup));
} else {
Backup();
}
}
public void Backup()
{
string databaseName = cbDatabase.Text;// getting the name of database for backup
SaveFileDialog1.ShowDialog(); // dialog will open
string backupFileName = SaveFileDialog1.FileName; // getting location of backup
//============ database query==================
SqlConnection con = new SqlConnection(conString);
con.FireInfoMessageEventOnUserErrors = true;
con.InfoMessage += OnInfoMessage;
con.Open();
query = string.Format("backup database {0} to disk = {1}", databaseName,backupFileName);
using (cmd == new SqlCommand(query, con)) {
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery();
}
con.Close();
con.InfoMessage -= OnInfoMessage;
con.FireInfoMessageEventOnUserErrors = false;
//============ Database operation end==================
}
private void OnInfoMessage(object sender, SqlInfoMessageEventArgs e)
{
lblStatusMsg.Text = e.Message; // mostly messages are like. 1 percent complete, 5 percent complete, 11 percent complete
foreach (SqlError info in e.Errors) {
if (info.Class > 10) {
// errror logging
} else {
Regex reger = new Regex("\\d+");
Match regerMatch = reger.Match(e.Message);
if (ProgressBar1.Value == 100) {
} else {
ProgressBar1.Value = regerMatch.Value;
}
}
}
}
Not responding issue until database operation completes
The purpose of the Invoke call is to have code run on the main thread. Your code is therefore creating a thread whose entire purpose is to force the main thread to run all the code.
Let's assume that you want to run a thread that, 10 seconds after it starts, updates a label's text to indicate completion. You still need to Invoke the label update, but that's the only thing that should be in the invoke.
In that case your thread function should look something like this:
private void MyThreadFunc()
{
// do something here
Thread.Sleep(10000);
// update the label:
if (label1.InvokeRequired)
Invoke(UpdateLabel);
else
UpdateLabel();
}
private void UpdateLabel()
{
label1.Text = "Something was finished.";
}
In other words, you need to separate out those things that have to run on the main thread (like anything that updates controls on your form) and Invoke only those bits. The rest of it should happen outside of the Invoke.
I guess I didn't make it clear.
The Invoke method is used to execute code in the context of the thread that owns the handle of the control or form that you're invoking on. You can use this to interact with controls on the UI, but you should only use it for that purpose. If you put all of the thread's close in an Invoke call then all of the thread's code will run in the UI thread, which makes it completely pointless to have a separate thread.
If you want to stop your application's UI from pausing while things happen - which is, after all, one of the main reasons to use a thread - then you should use the Invoke method only when absolutely necessary, and then only for very small sections of code. Call Invoke to update a control's parameters, interact with the non-threadsafe properties of the form, etc. You can use dialog boxes and so on directly from your other thread, although some prefer to use Invoke for those as well.
And if you're doing multiple invokes then you probably should write some helper methods to wrap the Invoke to clean things up. Something like:
public void Invoker(Action action)
{
if (InvokeRequired)
Invoke(action);
else
action();
}
public T Invoker<T>(Func<T> func)
{
if (InvokeRequired)
return (T)Invoke(func);
else
return func();
}
Now you can write your thread code with minimal impact like this:
public void ThreadFunc()
{
System.Threading.Thread.Sleep(1000);
Invoker(() => this.label1.Text = "Started");
for (int i = 1; i < 10; i++)
{
System.Threading.Thread.Sleep(1000);
Invoker(() => this.label1.Text = string.Format("Iteration {0}", i));
}
System.Threading.Thread.Sleep(1000);
Invoker(() => this.label1.Text = "Completed");
}
Or if you don't like lambda functions (for some reason) you can use methods like this:
public void Invoker<T>(Action<T> action, T p)
{
if (InvokeRequired)
Invoke(action, p);
else
action(p);
}
private void SetLabel(string value)
{
label1.Text = value;
}
And then in your code:
Invoker(SetLabel, "new text value");
The important part is to keep the code you're invoking be tiny or you'll end up blocking your main thread.

c# Thread which creates image getting cancelled

I have a event handler attached to the selectionChanged event on a DataGridView. In this handler I need to create and load an image and then display it in a picture box. The trouble I'm having is, if I jump between row selections quickly the application seems to hang, which is the issue I was trying to avoid.
Here is my code:
private void loadJobSheet(Job currentJob)
{
if (this.jobCardImageThread != null && this.jobCardImageThread.IsAlive)
this.jobCardImageThread.Abort();
Image jobCardImage = null;
this.jobCardImageThread = new Thread(new ThreadStart(
delegate()
{
SavedDocument document = currentJob.SavedDocument;
DocumentConverter<Bitmap> converter = DocumentConverterFactory<Bitmap>.getDocumentConverterForType(Path.GetExtension(document.Document_Name).Replace('.', ' ').Trim().ToUpper(), typeof(Bitmap));
jobCardImage = (Image)converter.convertDocument(FileUtils.createTempFile(document.Document_DocumentData.ToArray(), document.Document_Name));
}
));
jobCardImageThread.Start();
this.picLoadingJobCard.Visible = true;
jobCardImageThread.Join();
if (jobCardImage != null)
{
this.picJobCard.Image = jobCardImage;
this.picLoadingJobCard.Visible = false;
}
}
You are waiting for the separate thread to finish when you do
jobCardImageThread.Join();
This blocks the UI thread, which suspends the application.
You should remove the Join() call, create a separate method out of anything after the Join() call, and call that method from the delegate. Probably use an Invoke(...) call to switch back to the UI thread.
I think your problem is jobCardImageThread.Join(); With this statement you tell your Thread to wait for the other to finish. This way your UI is hanging.
Why don't you use a background-worker. For example:
Put this into your constructor
this.backgroundWorker = new BackgroundWorker();
this.backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
this.backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
this.backgroundWorker.WorkerSupportsCancellation = true;
And add the following methods:
private BackgroundWorker backgroundWorker;
private AutoResetEvent resetEvent = new AutoResetEvent(false);
private Thread thread;
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
this.picLoadingJobCard.Visible = true;
Job currentJob = (Job)e.Argument;
SavedDocument document = currentJob.SavedDocument;
DocumentConverter<Bitmap> converter = DocumentConverterFactory<Bitmap>.getDocumentConverterForType(Path.GetExtension(document.Document_Name).Replace('.', ' ').Trim().ToUpper(), typeof(Bitmap));
Image jobCardImage = (Image)converter.convertDocument(FileUtils.createTempFile(document.Document_DocumentData.ToArray(), document.Document_Name));
e.Result = jobCardImage;
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//error-handling
}
else if (e.Cancelled)
{
//cancel-handling
}
else
{
Image jobCardImage = e.Result as Image;
if (jobCardImage != null)
this.picJobCard.Image = jobCardImage;
}
this.picLoadingJobCard.Visible = false;
this.resetEvent.Set();
}
private void loadJobSheet(Job currentJob)
{
if (this.thread != null)
this.thread.Abort();
this.thread = new Thread(new ThreadStart(
delegate()
{
if (this.backgroundWorker.IsBusy)
{
this.backgroundWorker.CancelAsync();
this.resetEvent.WaitOne();
}
this.backgroundWorker.RunWorkerAsync(currentJob);
}));
this.thread.Start();
}
If you create a background Thread and immediately call Join after running it, you basically just wasted time and memory for creating a synchronous method, because your current thread will block until the background thread is finished. If the current thread is a UI thread, this will be pretty obvious.
Also, using Thread.Abort to kill a thread is not recommended.
I would suggest creating a long-lived background thread which will most of the time wait for a signal from the main thread. This will ensure that you don't unnecessarily create multiple threads in case you are receiving more requests than your worker method can handle.
This is the general idea:
// have a long lived and prosperous thread which handles jobs
private readonly Thread _backgroundWorker;
// you need a way to signal the thread to continue running
private readonly AutoResetEvent _signalNewTask;
// you need a flag indicating you want to stop (to avoid aborting the thread)
private volatile bool _keepRunning;
// and you need to pass the current job to that thread
private volatile Job _currentJob;
The loop should look something like this:
// this runs on a background thread
private void WorkerLoop()
{
Job lastJob = null; Image lastResult = null;
while (_keepRunning)
{
// use an AutoResetEvent for cross-thread signalization
_signalNewTask.WaitOne();
// make sure the app isn't ending
if (!_keepRunning)
break;
// don't bother if we already processed this job
if (lastJob == _currentJob)
continue;
// capture the job in a local variable
lastJob = _currentJob;
// long processing
lastResult = LoadImage(lastJob);
// check if this is still the last requested job
if (_keepRunning && lastJob == _currentJob)
DisplayImage(lastResult);
}
}
To schedule a job for execution, you simply set the field and signal the event:
private void ScheduleNewJob(Job nextJob)
{
// don't waste time if not needed
if (nextJob == _currentJob)
return;
_picLoadingJobCard.Visible = true;
_currentJob = nextJob;
_signalNewTask.Set();
}
You'll also need to add initialization and cleanup code to your Form:
public SomeForm()
{
InitializeComponent();
_keepRunning = true;
_signalNewTask = new AutoResetEvent(false);
_backgroundWorker = new Thread(WorkerLoop);
_backgroundWorker.IsBackground = true;
_backgroundWorker.Priority = ThreadPriority.BelowNormal;
_backgroundWorker.Start();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
// set the running flag to false and signal the thread
// to wake it up
_keepRunning = false;
_signalNewTask.Set();
// this will lock very shortly because the background
// thread breaks when the flag is set
_backgroundWorker.Join();
base.OnFormClosed(e);
}
And since DisplayImage (or whatever) will be called from a background thread, you have to schedule on the UI thread by calling Invoke:
private void DisplayImage(Image result)
{
if (this.InvokeRequired)
{
Invoke(new Action<Image>(DisplayImage), result);
return;
}
_picLoadingJobCard.Visible = false;
_picJobCard.Image = result;
}

Background worker, and cross thread issue

I have a Winforms application which is working fine.. using a BackgroundWorkerThread to manage GUI usability during processing of serial data to a device.
It's working fine.
Now, I am adding a new method, and copying what I did in other forms. But I am getting a cross thread exception.
I declare my BWT like this:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += DownloadGpsDataFromDevice;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
I then have a method delared like this, which does the background work:
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
Common.WriteLog("Extracting raw GPS data. Sending LL.");
ReplyString raw = DeviceServices.ExecuteCommand("$LL");
The DeviceServices.ExecuteCommand("$LL"); is the bit that does the work, but I am getting the exception on the previous line, where I log to a text file. Now, that makes you worry - writing to a file. However, I have done this thousands of times in another BWT.
I made the writing thread safe. Here this my Common.WriteLog method:
public static void WriteLog(string input)
{
lock (_lockObject)
{
WriteLogThreadSafe(input);
}
}
private static void WriteLogThreadSafe(string input)
{
Directory.CreateDirectory(LogFilePath);
StreamWriter w = File.AppendText(LogFilePath + #"\" + LogFileName);
try
{
w.WriteLine(string.Format("{0}\t{1}", DateTime.Now, input));
}
catch (Exception e)
{
System.Console.WriteLine("Error writing to log file!");
System.Console.WriteLine("Tried to write: [" + input + "]");
System.Console.WriteLine("Failed with error: [" + e.Message + "]");
}
finally
{
w.Close();
}
}
This have been working for ages. I don't believe the error is there. I think I am just missing something on the call maybe?
You cannot change UI elements from BackgroundWorker thread. You'll have to marshall back to UI thread by calling Invoke().
Try this
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
Invoke((MethodInvoker)(() => {
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
});
...
The issue is that you are updating UI elements from non-UI thread:
Those lines should not be inside of DownloadGpsDataFromDevice
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
To take advantage of BackgroundWorker run method bw.ReportProgress(0);. Update UI in ProgressChanged handler, which was specifically designed for this purpose.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage = 0)
{
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
}
}
Some instances can't or should not be accessed by multiple threads. You have two options to protect your data from cross thread exceptions.
You can lock your object when you access it from multiple threads with a lock:
object locker = new object();
SomeObject MyObject = new SomeObject();
private void FromMultipleThread()
{
lock(locker)
{
MyObject = OtherObject;
}
}
Your second option is to lock your thread with a ManualResetEvent. It is very simple, you only have to call WaitOne() from you ManualResetEvent to lock your thread while an other thread access your "cross threaded" Object.
In your case, you would want to change your UI from the reportProgress of your backgroundWorker. The reportProgress will come back to the initial thread, then you can modify your UI.
Are you sure that is the right line? I don't think you should be able to update the ui in your worker. Try commenting out the gui update and clean and build your solution to see if the logging is really the problem. To update the ui, set WorkerReportsProgress and create an event handler for that to update the ui and report progress in the worker.

Best pattern for "Do some work and quit"

I'm currently writing a little GUI program that does some work and exits afterwards. While work is done, the GUI thread is updated with infos for the user.
This is the pattern I'm currently using and I'm thinking it's not the most elegant one:
static void MainForm_Loaded(BeoExport exporter)
{
// Thread 1 runs the Export
workerThread = new Thread(() =>
{
exporter.StartExport();
// don't exit immediately, so the user sees someting if the work is done fast
Thread.Sleep(1000);
});
// Thread 2 waits for Thread 1 and exits the program afterwards
waiterThread = new Thread(() =>
{
workerThread.Join();
Application.Exit();
});
workerThread.Start();
waiterThread.Start();
}
So what pattern/mechanics would you use to do the same?
To clarify: I was not interested in a way to update the GUI thread. That's already done. This might sound esoteric but I was lookig for the right way to quit the application.
If I could, I would give Dave the credits, since he pointed out the usefulness of the BackgroundWorker.
Have you considered a BackgroundWorker thread instead? You can use its ReportProgress method and ProgressChanged event to update the GUI (with a progress bar perhaps), assuming that you can refactor BeoExport.StartExport method to also report progress. This gives the users visible feedback that work is actually happening.
I don't understand why do you use two threads. You can use threadpool:
ThreadPool.QueueUserWorkItem((state)=>{
exporter.StartExport();
Thread.Sleep(1000);
Application.Exit();
});
I suggest you to use the BackgroundWorker class. It's thought to do the kind of job you're doing. You could do domething like this:
public class Form1 : Form
{
private BackgroundWorker worker;
private ProgressBar bar;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
bar= new ProgressBar();
bar.Dock = DockStyle.Top;
Controls.Add(bar);
worker = new BackgroundWorker();
worker.WorkerReportsProgress=true;
worker.RunWorkerCompleted += delegate
{
Close();
};
worker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs ev)
{
bar.Value = ev.ProgressPercentage;
};
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//do your work here. For the example, just sleep a bit
//and report progress
for (var i = 0; i < 100;i++ )
{
Thread.Sleep(50);
worker.ReportProgress(i);
}
}
}
You can use an AutoResetEvent. The main thread waits for the autoreset event to be reset.
var wh = new AutoResetEvent(false);
var workerThread = new Thread(() =>
{
exporter.StartExport();
// don't exit immediately, so the user sees something if the work is done fast
Thread.Sleep(5000);
wh.Set();
});
workerThread.Start();
wh.WaitOne();
Application.Current.Shutdown();
Have you taken a look at the Task Parallel Library in .net 4 you can set up a task and the library will work out to best pararellise it for you, either threading, working a seperate CPU core's the is a load of great information about it online.
Regards
Iain
To add a little to Lain's answer, here's a Console sample using a Task from the System.Threading.Tasks namespace.
class Program
{
static void Main(string[] args)
{
Task<int> task = Task<int>.Factory.StartNew(() =>
{
Exporter exporter = new Exporter();
int i = exporter.StartExport();
return i;
});
int iResult = task.Result;
Console.WriteLine(iResult);
Console.ReadLine();
}
class Exporter {
public int StartExport()
{
//simulate some work
System.Threading.Thread.Sleep(500);
return 5;
}
}
}
Using a BackgroundWorker might help you implement your background processing. If you wanted to stick with your current pattern then consider the following.
static void MainForm_Loaded(BeoExport exporter)
{
workerThread = new Thread(() =>
{
exporter.StartExport();
Thread.Sleep(1000);
MainForm.BeginInvoke(
(Action)(() =>
{
MainForm.Close();
});
});
workerThread.IsBackground = true;
workerThread.Start();
}
Have the worker thread send a notification message of some description to the main thread. The GUI can then either exit or display a "done" message as appropriate.

Categories