views:

180

answers:

4

I'm trying to write multithreading code and facing some synchronization questions. I know there are lots of posts here but I couldn't find anything that fits.

I have a System.Timers.Timer that elapsed every 30 seconds it goes to the db and checks if there are any new jobs. If he finds one, he executes the job on the current thread (timer open new thread for every elapsed). While the job is running I need to notify the main thread (where the timer is) about the progress.

Notes:

  1. I don't have UI so I can't do beginInvoke (or use background thread) as I usually do in winforms.
  2. I thought to implement ISynchronizeInvoke on my main class but that looks a little bit overkill (maybe I'm wrong here).
  3. I have an event in my job class and the main class register to it and I invoke the event whenever I need but I'm worrying it might cause blocking.
  4. Each job can take up to 20 minutes.
  5. I can have up to 20 jobs running concurrently.

My question is:

What is the right way to notify my main thread about any progress in my job thread?

Thanks for any help.

+1  A: 

Use events. The BackgroundWorker class, for example, is designed specifically for what you have in mind.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

The ReportProgress function along with the ProgressChanged event are what you would use for progress updates.

pullJobTimer.Elapsed += (sender,e) =>
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.DoWork += (s,e) =>
    {
        // Whatever tasks you want to do
        // worker.ReportProgress(percentComplete);
    };
    worker.ProgressChanged += mainThread.ProgressChangedEventHandler;
    worker.RunWorkerAsync();
};
Jake
I feel it is wrong to use the BackgroundWorker and i can explain why.System.Timers.Timer pullJobTimer = new System.Timers.Timer(INTERVAL); pullJobTimer.Elapsed += new System.Timers.ElapsedEventHandler(PullQJobs);I can open a new BackgroundWorker in pulljobs that's true and then i will have ProgressChanged. But timer elapsed opens a new thread and then i will open a new backgroundworker thread ??? feels wrong no? Or maybe i didn't get your idea.
UshaP
You don't even have to open a new thread. BackgroundWorker does it for you when you run it asynchronously via `RunWorkerAsync()`. This also does not present a memory leak, as Jim Mischel suggested, because the event subscription gets destroyed with the BackgroundWorker instance when it's garbage collected.
Jake
I know that the backgroundworker opens a thread but also the timer. So Main class use Timer -> Timer opens a new thread for each elapsed -> then i run backgroundworker that opens another thread.And that is what makes me feel uncomfortable.
UshaP
Also note that the `ProgressChanged` event handler will run in a thread pool worker thread and not on the application main thread.
João Angelo
Why are you opening a new thread for each `Elapsed`? If you only subscribe `Elapsed` to an event handler like I've provided above, it will only spawn one thread each time it fires, namely that created by `RunWorkerAsync`.If I'm misunderstanding you, can you please post the event handler you're currently using for Timer.Elapsed?
Jake
@Jake, "`System.Timers.Timer` class will, by default, call your timer event handler on a worker thread obtained from the common language runtime (CLR) thread pool."
João Angelo
@João, ok, now I understand, but I think this is still a fairly small price to pay especially considering it all happens automatically and the thread will exit immediately after calling `RunWorkerAsync()`. The `BackgroundWorker` approach still accomplishes the task at hand using a widely-used, heavily-tested library designed specifically for this purpose. The other options (putting ALL of the synchronization logic in the `Elapsed` handler or making each worker depend on the main thread) are poor design decisions in my opinion, because they increase coupling without providing any benefit.
Jake
@Jake, in this specific case the `BackgroundWorker` provides no added value. It starts a new thread, which is not needed and the `ProgressChanded` will just run in another worker thread which again, provides no added value and does not use any synchronization mechanism.
João Angelo
A: 

You can notify the main thread of progress through a callback method. That is:

// in the main thread
public void ProgressCallback(int jobNumber, int status)
{
    // handle notification
}

You can pass that callback method to the worker thread when you invoke it (i.e. as a delegate), or the worker thread's code can "know" about it implicitly. Either way works.

The jobNumber and status parameters are just examples. You might want you use some other way to identify the jobs that are running, and you may want to use an enumerated type for the status. However you do it, be aware that the ProgressCallback will be called by multiple threads concurrently, so if you're updating any shared data structures or writing logging information, you'll have to protect those resources with locks or other synchronization techniques.

You can also use events for this, but keeping the main thread's event subscriptions up to date can be a potential problem. You also have the potential of a memory leak if you forget to unsubscribe the main thread from a particular worker thread's events. Although events would certainly work, I would recommend the callback for this application.

Jim Mischel
Jim, I think this is sync call while i need async call
UshaP
The progress callback is called on the worker thread, not on the main thread. So it's an async call from the perspective of the main thread.
Jim Mischel
+1  A: 

If you don't mind depending on .NET 3.0 you can use the Dispatcher to marshal requests between threads. It behaves in a similar way to Control.Invoke() in Windows Forms but doesn't have the Forms dependency. You'll need to add a reference to the WindowsBase assembly though (part of .NET 3.0 and newer and is basis for WPF)

If you can't depend on .NET 3.0 then I'd say you were onto the correct solution from the beginning: Implement the ISynchronizeInvoke interface in your main class and pass that to the SynchronizingObject property of the Timer. Then your timer callback will be called on the main thread, which can then spawn BackgroundWorkers that checks the DB and runs any queued jobs. The jobs would report progress through the ProgressChanged event which will marshal the call to the main thread automatically.

A quick google search revealed this example on how to actually implement the ISynchronizeInvoke interface.

Isak Savo
+1  A: 

You can also use lock to implement a thread-safe JobManager class that tracks progress about the different worker threads. In this example I just maintain the active worker threads count, but this can be extended to your progress reports needs.

class JobManager
{
    private object synchObject = new object();

    private int _ActiveJobCount;

    public int ActiveJobsCount
    {
        get { lock (this.synchObject) { return _ActiveJobCount; } }
        set { lock (this.synchObject) { _ActiveJobCount = value; } }
    }

    public void Start(Action job)
    {
        var timer = new System.Timers.Timer(1000);

        timer.Elapsed += (sender, e) =>
        {
            this.ActiveJobsCount++;
            job();
            this.ActiveJobsCount--;
        };

        timer.Start();
    }
}

Example:

class Program
{
    public static void Main(string[] args)
    {
        var manager = new JobManager();

        manager.Start(() => Thread.Sleep(3500));

        while (true)
        {
            Console.WriteLine(manager.ActiveJobsCount);

            Thread.Sleep(250);
        }
    }
}
João Angelo