views:

208

answers:

8

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.

+2  A: 

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.

Matthew Ferreira
Hi Matthew, updating the GUI is not the point. It's already done. What bugs me is that there's a second threat that's only waiting for the first thread.
VVS
BackgroundWorker can trigger an event when it's complete so you can close in response to that.
Dave
@Dave. Ah, missed that part. Big plus for BackgroundWorker :)
VVS
Another plus is that it's ReportProgress method fires asynchronously so you don't have to worry about updating the GUI blocking your worker thread. Also, its events for ProgressChanged and completion are received on the thread the original call to RunWorkerAsync was on, so you don't have to worry about switching back to the UI thread to update the GUI
Dave
+2  A: 

I don't understand why do you use two threads. You can use threadpool:

ThreadPool.QueueUserWorkItem((state)=>{
    exporter.StartExport();
    Thread.Sleep(1000);
    Application.Exit();
});
TcKs
Weird. I think I'm guilty of just another lame question caused by missing oxygen. I was totally sure that this throws some Thread*Exception but that's not the case as my test just a minute ago showed. Never assume.. :)
VVS
I'm just wondering if it's really ok to quit the application in a worker thread at all.
VVS
The "Application" class is not UI object, so you don't need "touch" them in UI thread. However, if you want call it in UI thread, use SynchronizactionContext class or Control.BeginInvoke or Control.Invoke method.
TcKs
+2  A: 

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);
        }
    }

}
Andrea Parodi
The interesting part for me was how to quit elegantly. BackgroundWorker.RunWorkerCompleted is what makes it interesting for the job.
VVS
VVS: in my example, I'm quitting by calling Form.Close(). This work if this is the main form of your application, otherwise you can call Application.Exit() as in your code.
Andrea Parodi
@Andrea: Oh, that "Close()" was there from the beginning? *rubs eyes* Apologies.. time to go home.
VVS
You're welcome :)
Andrea Parodi
A: 

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.

Anthony Williams
+1  A: 

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();
Francesco De Vittori
It is important to note that with this pattern the main thread blocks until the worker thread is complete. If the main thread also doubles as the UI thread (as it does for the OP) then the GUI will hang up. Another interesting perspective here is that if you are going to wait for the worker thread to complete then why put the work in another thread to begin with.
Brian Gideon
@Brian: Good point. So the whole reason for a worker thread is nullified.
VVS
@VVS: No, I wouldn't say that. If your whole reason is to get the work off the UI thread so that the UI remains responsive then you'll definitely want to continue with the worker thread approach. It's just that this specific implementation will not lead you to your goal.
Brian Gideon
@Brian: Um, that's what I wanted to say: if I would use an AutoResetEvent I would hang the GUI thread and nullify the usefulness of the worker thread. :-)
VVS
You're right, I had not realized that the AutoResetEvent blocks the UI thread. Sorry for the mistake.
Francesco De Vittori
+1  A: 

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

Iain
+1  A: 

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;
            }
        }
    }
Conrad Frix
+1  A: 

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();
} 
Brian Gideon