tags:

views:

60

answers:

2

I have an object that can take anywhere from a split second to a couple of minutes to initialize. The reason being is that the constructor retrieves data from a webservice that could be a few kilobytes to several megabytes, and depending on the user's connection speed performance may vary greatly. For that reason I am wanting to put events in that will handle progress notification.

Here is my question: Can I put event handlers in the constructor or should this type of action be done with a Load method?

For example:

public class MyObject
{    
 public event EventHandler<UpdateLoadProgressEventArgs> UpdateLoadProgress;    

 public MyObject(int id)
 {
   Background worker bgWorker = new BackgroundWorker();
   bgWorker.DoWork += delegate(object s, DoWorkEventArgs args)
   {
        //load data and update progress incrementally
        UpdateLoadProgress(this, new UpadteLoadProgressEventArgs(progressValue));

        Result = someValue;         
   }
   bgWorker.RunWorkAsync();

 } 

 public int Result
 {
  get;
  set;
 }

} 

However when I try to tie the event handlers to the constructor they are always null when being called:

MyObject o = new MyObject(1);
o.UpdateLoadProgress += new EventHandler<EventArgs>(o_UpdateLoadProgress);

I assume this happens because I wire up the events after the constructor. The only alternative I see is creating a Load method that does the work of the constructor. The downside is that anyone that uses this class must know to call Load before trying to access Result (or any other property).

EDIT: Here is the final solution:

MyObjectBuilder Class

public class MyObjectBuilder
    {
        public event ProgressChangedEventHandler ProgressChanged;

        public MyObject CreateMyObject()
        {
            MyObject o = new MyObject();
            o.Load(ProgressChanged);

            return o;
        }
    }

MyObject Class

public class MyObject 
    {
        public int Result { get; set;}

        public void Load(ProgressChangedEventHandler handler)
        {
            BackgroundWorker bgWorker = new BackgroundWorker();
            bgWorker.WorkerReportsProgress = true;
            bgWorker.ProgressChanged += handler;

            bgWorker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(10);
                    Result = i;

                    bgWorker.ReportProgress(i);
                }
            };
            bgWorker.RunWorkerAsync();                      
        }
    }

Program Class

class Program
    {
        static void Main(string[] args)
        {
            MyObjectBuilder builder = new MyObjectBuilder();
            builder.ProgressChanged += new ProgressChangedEventHandler(builder_ProgressChanged);        

            MyObject o = builder.CreateMyObject();
            Console.ReadLine();
        }

        static void builder_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Console.WriteLine(e.ProgressPercentage);
        }
    }
+6  A: 

Another option would be to pass the event handlers into the constructor too, of course.

Personally I try to avoid doing something like this within a constructor. Creating a new object shouldn't generally start background tasks, IMO. You may want to put it into a static method instead - which can call a private constructor, of course.

You could also split up your class into two - a builder which gets everything ready (e.g. the events), and then an "in-flight or completed" class, which has the Result property. You'd call Start or something similar on the first class to get an instance of the second.

Jon Skeet
@Jon I attempted a stab at your idea of using a Builder class. I don't think I understand the concept though. I pass in the event handler to MyObjectBuilder and then return a MyObject back with a create method. Unfortunately MyObject is still not fully loaded. Any tweaks you would make to my above code?
Blake Blackwell
@Blake: Well, I wouldn't bother having the event as part of MyObject - I'd just pass the handler into the constructor (from MyObjectBuilder.CreateMyObject). However, that looks like it should basically work... what's happening? (Note that you're blending *both* approaches in that code - if you're happy for your sample code to pass the event handler in to start with, you could do it all in one type, maybe with a static method. The idea of the builder was to let you construct it, add the event handlers, and *then* call the Create method.)
Jon Skeet
@Jon The main issue is that at the point of Console.WriteLine(o.Result) the output is 0 instead of the expected 99 (after the loop has completed). The progress is updating correctly, its just that the object isn't fully initialized. Is having the object fully initialized a design goal I should have, or is that understood by the builder pattern that it is still in the process of being built?
Blake Blackwell
@Jon - I corrected to more properly use the builder pattern so that I wasn't blending the two approaches. Please let me know if that more correctly matches what you were thinking.
Blake Blackwell
@Blake: Hmm... After the loop has finished Result *should* be updated properly. Were you definitely only checking it after the background worker had finished? Admittedly there are issues around variable volatility etc (you may want to make your property a full property backed by a volatile variable) but I'd expect it to work. I can't investigate it right now, but I'll have a look later. Note that in the code you've shown, you're printing Result *straight away* - i.e. before the background worker has finished. I would expect that to print 0.
Jon Skeet
@Jon - Thanks for all your help! If I add a RunWorkerCompleted event handler than the Result value is correctly displayed as 99. I guess my only other question is whether I should block on o.Load(ProgressChanged) call so that the new MyObject is not returned until it has been fully loaded. Your thoughts on that is much appreciated. My concern is that another developer down the road will not realize that the object isn't fully created till the background process completes.
Blake Blackwell
@Blake: If you don't want the call to return until the "background" task has completed, it isn't really a background task any more, is it? You might as well run the code in the same thread to start with. There are ways you could make `Result` block until it has a useful value - but I suspect you should take another think at what exactly you're trying to achieve. (If you're using .NET 4, Parallel Extensions can make some of this easier - but you still need to work out your goals first.)
Jon Skeet
@Jon - Yeah, I agree. I think I was confusing my goals of having an asynchronous process and a synchronous process at the same time. I can make the necessary adjustments in my code and work with it being asynchronous. Thanks for your help!
Blake Blackwell
+1  A: 

Is this possible? Maybe. Is it wise? No.

The most obvious reason is that you aren't guaranteed that the background thread will not execute before the event handlers are wired up after the object's construction. Or worse, some of the event handlers may be subscribed while others are not.

villecoder