views:

86

answers:

6

Hi-

I'm a newbie to C# and multithreading so I apologise if this is a duplicate question but being a novice it appears my question is slightly different to others I have read.

My GUI runs in one (main) thread. It calls a background task (in a dll -- that I am writing too) that runs in a separate thread. The dll has NO knowledge of the GUI (i.e. it cannot reference the GUI class).

Now, say I want to update a progress bar on the GUI based on the status of the dll thread -> What I'm doing is creating an event in the dll that every X per cent will fire and the GUI will subscribe to this event. When the event is fired, the GUI will update the progress bar.

My questions:

  1. Is the method of creating events the best way (bearing in mind the dll cannot reference the GUI)?
  2. How do I ensure my above method is 'event safe'? Should I pass the progress percentage in the event to be thread safe or is there more to it?
  3. Do I need to use Invoke when updating the GUI? I saw a post that suggested I did but I don't understand why since the updating of the bar is being done in the GUI thread??!

Hope you can clarify this for me!

Thanks

+3  A: 

Look into the BackgroundWorker class. It sounds like it fits your scenario pretty well.

This link on MSDN explains how to use it: http://msdn.microsoft.com/en-us/library/cc221403%28VS.95%29.aspx

Dismissile
The BackgroundWorker is trying help you solve the task of running background operations without requiring any real knowledge about how threads and the UI work, but isn't really offering much help if you do understand how events are raised and how to use the Dispatcher. I would highly recommend anyone that wants to do multi threaded code to at least attempt to understand what's going on under the hood of an abstraction such as the background worker.
Freed
I agree that it's important to understand what is going on underneath the hood, but I think that BackgroundWorker is probably the safest way to implement it if you don't understand what is going on underneath the hood.
Dismissile
A: 

To answer (3) you will need to use Invoke. The event-handlers are going to be run from the background thread, not the GUI thread.

Paul Equis
+3  A: 

Keep in mind that under most circumstances, the events raised from your background task will also run on the background thread. No thread context switch happens automatically at all.

To understand why, you have to consider what an event is; just a certain type of Delegate object. You are setting a Delegate to that event from the main thread... but that delegate will actually be called within the background thread, in the code that triggers the event.

So yes; you would need to make sure you are moving things over to run on the GUI thread from within that event handler.

Andrew Barber
That nails it: "but that delegate will actually be called within the background thread, in the code that triggers the event."
Devesh
A: 

If you spin off a thread, you need to create a delegate, that can safely invoke your main thread with the appropriate parameters.

delegate void UpdateDelegate(int val)
void Update(int val)
{
  if(this.InvokeRequired())
  {
     Invoke(new UpdateDeleage(Update),new object[] {val});
     return;
  }
  this.MyProgressBar.Value = val;
}

Call Update from your separate thread as you would if calling it from your main thread. Once the thread determines that your main thread needs invoked to pass the value, it will invoke it with your delegate, with the parameters you passed. Otherwise, it will simply skip the block and set your values.

e.g.

...

new Thread(()=>IncrementValues()).Start();

...

void IncrementValues()
{
   while(true)
   Update(new Random(0,10));
}
George
+1  A: 

1.-I use that method all the time and yes it will work

2.-Just pass a int to the event handler and the variable will be safe to read. however when you are fireing the event from code do it like this

private void UpdatePercentage(int a)
{
    var myEvent = PercentageUpdatedEvent
    if(myEvent != null)
         myEvent(this, new ProgressBarEventArgs(a));
}

The reason for this is so if the event is unsubcribed between the null check and the calling you won't get a exception.

3.-As everyone else has mentioned you will need to call Invoke as the event will be running on the dll's thread. However with controls it is legal to call a BeginInvoke without a EndEnvoike so the call will be non blocking on the dll's thread.

Here is the pattern I always use

private myClass_OnPercentageUpdatedEvent(object a, ProgressBarEventArgs e)
{
    if(progressBar.InvokeRequired)
        progressBar.BeginInvoke((Action<object,EventArgs)myCless_OnPercentageUpdatedEvent, a, e);
    else
    {
        progressBar.Value = e.Value;
    }
}
Scott Chamberlain
This really helped. Many thanks. It finally clicked (probably obvious to all of you) that even though the event handler is written in the GUI class, it is still running in the dll thread as that where it is 'called' from! Now I get why we need Invoke and this clarifies it. I agree with other posts that I needed to understand why/how it works rather than use BackgroundWorker, etc.
Devesh
A: 

I have on my blog a few different approaches to this problem, with the advantages/disadvantages of each. In summary, I recommend using the Task class.

Stephen Cleary