3

I have a background worker in my GUI class.

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    ProgressClass obj = new ProgressClass();
    Importer tradeImporter = e.Argument as Importer;
    BackgroundWorker worker = sender as BackgroundWorker;
    List<TradeUploadInfo> list = obj.AllocateTrades2(tradeImporter, false);
    e.Result = list; //Passes the list for processing
}

Importer is my own class. Now, the AllocateTrades2 method has all the processing done in it.

My question is, how would I go about performing a bw.ProgressReport inside the AllocateTrades2 method, which is in a different class without passing the bw as a parameter?

Would be great if someone explained it to me how to do it with events, but if there is another elegant way. I'm open for ideas.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Alexey
  • 3,607
  • 8
  • 34
  • 54

3 Answers3

5

If you don't want to pass in the entire BGW (justifiably so) so as to not expose more than it needs to know, one option is to just pass in a delegate that you assign a ReportProgress call to.

Adjust the signature of AllocateTrades2 to be:

public List<TradeUploadInfo> AllocateTrades2(
    Importer importer, bool flag, Action<int> reportProgress)

Invoke the reportProgress delegate as appropriate from within that method.

Then adjust the call to AllocateTrades2 like so:

obj.AllocateTrades2(tradeImporter, false,
     progress => worker.ReportProgress(progress));
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Is progress a variable? And if yes, of what type? Int? – Alexey May 21 '13 at 19:43
  • @Alexey It's a parameter to that anonymous function. The type is `int`, yes. It's based on the definition of the delegate which is defined in: `Action`. You could change the type there to whatever you wanted. – Servy May 21 '13 at 19:45
  • 1
    I get a Delegate 'System.Func' does not take 1 arguments error. – Alexey May 21 '13 at 19:45
  • If you are using .NET 4.5 they added a nice Interface for this situation [IProgress](http://msdn.microsoft.com/en-us/library/hh138298.aspx). They also include a class [Progress](http://msdn.microsoft.com/en-us/library/hh193692.aspx) which you can pass in. (I guess it is more useful when you are doing more manual threads than a background worker as it does it's own synchronization handling, but it is useful to know about.) – Scott Chamberlain May 21 '13 at 19:55
  • 1
    @ScottChamberlain `Progress` is essentially a replacement for the related aspects of BGW. I considered mentioning it, but in this case he's already leveraging BGW for most of that work. That's certainly another valid option though, and certainly worth using if you wanted to scrap the BGW entirely (for example if using the TPL instead). – Servy May 21 '13 at 19:58
  • "Invoke the reportProgress delegate as appropriate from within that method." I'm having troubles with that. How do I call a delegate from inside a method? – Alexey May 21 '13 at 21:01
  • 1
    @Alexey `reportProgress(someIntRepresentingTheCurrentProgress)` – Servy May 21 '13 at 21:03
  • What will Invoke(reportProgress) do? – Alexey May 21 '13 at 21:05
  • @Alexey Run it on the UI thread. You don't want to be doing that. 1) You need to be passing it the int representing progress; if you did that, it wouldn't know what to pass. 2) There's nothing about this that requires it being run on the UI thread. – Servy May 21 '13 at 21:07
1

Well, given the fact that AllocateTrades2 runs in the context of the background worker, any events that it raises are also executed in that context.

So, all you need to do is add a new event in your ProgressClass, say NotifyProgress, and bind it to the class where you have the background worker.

So:

 //In class ProgressClass. 
 public event EventHandler<ProgressClassEventArgs> NotifyProgress = (s, e) => {};

And next:

 private void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     ProgressClass obj = new ProgressClass();

     //Here you hook up the event
     obj.NotifyProgress += this.OnProgressChanged;

     Importer tradeImporter = e.Argument as Importer;
     BackgroundWorker worker = sender as BackgroundWorker;
     List<TradeUploadInfo> list = obj.AllocateTrades2(tradeImporter, false);
     e.Result = list; //Passes the list for processing
 }

The event handler would look like this:

private void OnProgressChanged(object sender, ProgressClassEventArgs e) 
{
   worker.ReportProgress(e.Progress);
}

It's OK, since you can (or you already do) have the worker as a member in this class.

You will need to define the ProgressClassEventArgs (EventArgs subclass) and add a Progress property of type int in this case, to match the ReportProgress args.

Marcel N.
  • 13,726
  • 5
  • 47
  • 72
1

If you are able/willing to modify the obj.AllocateTrades2 method, you can yield results and then add each item to your list in a loop.

Example:

public IEnumerable<TradeUploadInfo> AllocateTrades2(Importer tradeImporter, bool foo)
{

    foreach( ... )
    {
        TradeUploadInfo bar; // = ...
        // ...
        yield return bar;
    }

}

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    ProgressClass obj = new ProgressClass();
    Importer tradeImporter = e.Argument as Importer;
    BackgroundWorker worker = sender as BackgroundWorker;
    List<TradeUploadInfo> list = new List<TradeUploadInfo>();
    foreach ( TradeUploadInfo info in obj.AllocateTrades2(tradeImporter, false) )
    {
        list.Add( info );
        // ... progress
    }
    e.Result = list; //Passes the list for processing
}

The beauty here is that you can use AllocateTrades2 exactly as you did before (meaning you don't have to modify existing code or overload the function) (hmm.. actually, you would need to modify code that was explicitly expecting a List, probably by just adding .ToList() after the function call) and you don't need to add events (which can get a little tricky when it comes to garbage collection).

Community
  • 1
  • 1
JDB
  • 25,172
  • 5
  • 72
  • 123