Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a method that reports progress

I'm writing a class that performs a certain operation in a library. But the operation is tedious, and I want to be able to find out the progress of the method inside that class so that I can use it in a WinForms application to report the progress.

I'm planning to run my class on another thread in my WinForms application and I want the class to be separated from the concerns of the WinForms application, and I don't want to bind it to anything specific other than what it does.

What would be the best way to implement a progress reporting mechanism in a library class?

Would it be a good idea to somehow have a progress variable in the class, and add an event listener to it in my WinForms application? And if it is, how can I do it?

Edit: I have used the BackgroundWorker class before, but my problem is I don't want my library class to be concerned with any of the multithreading operations. So I don't want to invoke ReportProgress in the library class, I want to (maybe) have a variable in the class that contains the current progress and I want the UI thread to somehow "subscribe" to it. I don't know if that's a good way to design it though.

like image 312
hattenn Avatar asked Dec 05 '12 04:12

hattenn


2 Answers

Look into the BackgroundWorker class. It supports automatic marshaling across threads for progress reporting, and it has a very simple event model for this kind of support.

Edit: Given your position on using BackgroundWorker directly, what you might do is create a simple wrapper:

// Error checking elided for expository purposes.
public interface IProgressReporter 
{
    void ReportProgress(int progress, object status);
}

public class BackgroundWorkerProgressReporter : IProgressReporter
{
    private BackgroundWorker _worker;
    public BackgroundWorkerProgressReporter(BackgroundWorker worker)
    {
        _worker = worker;
    }

    public void ReportProgress(int progress, object status)
    {
        _worker.ReportProgress(progress, status);
    }
}

Then, alter the constructor (or add a property) of the class that you want to report progress to accept an IProgressReporter. This is a form of dependency injection, and should meaningfully allow your object to report progress whilst also avoiding specific dependencies on threading libraries.

like image 179
Rob Avatar answered Sep 28 '22 06:09

Rob


Here you can do 2 different ways I will post both options below to help point you in the right direction

You should be doing this on another thread, and then updating your UI thread from that thread. You are blocking further processing by performing this work on the UI thread.

If you can't move this code to the UI thread, then you could always call Application.DoEvents, but I strongly suggest you explore these options first:

  • System.ComponentModel.BackgroundWorker
  • System.Threading.ThreadPool
  • System.Threading.Thread
  • System.Threading.Tasks namespace

Second Alternative you could do something like this:

You'll need to get your data from one thread to the other. This can be done a couple ways...

First, your "background" thread could update some kind of "CurrentStatus" string variable that it changes as it goes along. You could then put a timer on your form that would then grab the CurrentStatus variable and update the label with it.

Second, you could simply invoke the operation from the background thread to the UI thread with a delegate using the InvokeRequired property of the label control. So for example...

private delegate void UpdateStatusDelegate(string status);
private void UpdateStatus(string status)
{
    if (this.label1.InvokeRequired)
    {
        this.Invoke(new UpdateStatusDelegate(this.UpdateStatus), new object[] { status });
        return;
    }

    this.label1.Text = status;
}

You can call that UpdateStatus() method from any thread (UI or background) and it will detect whether or not it needs to invoke the operation on the main UI thread (and if so, does it).

Edit: To actually set up the thread, you can do so like this:

    private void StartProcessing()
    {
        System.Threading.Thread procThread = new System.Threading.Thread(this.Process);

        procThread.Start();
    }

    private void Process() // this is the actual method of the thread
    {
        foreach (System.IO.FileInfo f in dir.GetFiles("*.txt"))
        {
            // Do processing
            // Show progress bar
            // Update Label on Form, "f.Name is done processing, now processing..."
            UpdateStatus("Processing " + f.Name + "...");                
        }
    }

Then when the user clicks the "GO" button you'll simply call StartProcessing().

like image 24
MethodMan Avatar answered Sep 28 '22 06:09

MethodMan