The BackgroundMultiWorker is modelled on the BackgroundWorker but provides support for multiple background operations. Each task is represented by a token, which can be any object at all. Just note that two value type objects with the same value will be recognised as the same token. As a result, using numbers or strings as tokens is not recommended if they may be duplicated. Instead, create a reference type object with a single property of the desired type.
The members of the BackgroundMultiWorker closely resemble those of the BackgroundWorker class, except that most either require or provide a token to identify the current task. For instance, when calling RunWorkerAsync to start a new operation, you pass in a token to identify that operation. When the DoWork event is raised, that token is returned via the Token property of the DoWorkEventArgs object received by the event handler. This allows you to determine which task you're doing the work for.
The CancellationPending and IsBusy properties of the BackgroundWorker have been translated to the IsCancellationPending and IsBusy methods of the BackgroundMultiWorker. Both are overloaded to allow you to determine the overall state or the state for a particular task, identified by a token.
Finally, ReinstateCancelledOperation and ReinstateCancelledOperations methods have been added, allowing you to "uncancel" a cancelled task.
I haven't actually tested the code yet but I thought I'd post it anyway. I'll get to testing it myself soon everyone is welcome to view the code and put it to use if they're happy to fix any issues that arise themselves.
Note that the solution was created in VS 2010 targeting .NET 2.0. If you're using an earlier version of VS, you should be able to just create your own Class Library project and import the code files. In that case, you will need to reference System.Drawing.dll for the ToolboxBitmap attribute.
UPDATES:
1.0.0.1: Added DefaultEvent attribute and test application.
Last edited by jmcilhinney; Mar 17th, 2011 at 01:00 AM.
Hi
Do you have some sample code to demonstrate how to use this submission?
Thanks
Allan
If you know how to use a BackgroundWorker then you know how to use a BackgroundMultiWorker. If you don't know how to use a BackgroundWorker then there's plenty of information around on that topic, including my own thread in this very CodeBank. You can find that thread via the CodeBank link in my signature.
I have updated the attachment in post #1. The BackgroundMultiWorker class itself remains unchanged except for the addition of the DefaultEvent attribute. This means that you can now double-click an instance in the designer to generate a handler for the DoWork event, just as you can with the BackgroundWorker class.
The big news is that I've added a demo/test application. It lets you choose a number of tasks to run and then start that many tasks using the one BackgroundMultiWorker instance. It will display a row for each task in a grid and display the elapsed time as the tasks proceed. You can cancel individual tasks or all remaining tasks, receiving a prompt for each one. Finally, the progress rows are transferred to another grid that displays completed tasks, whether they were cancelled and, if not, the total elasped time.
for the sake of those of us still using vb2008 & 2005, can you port your code to those enviroment?
Just create a new project and import the code files. If there is VB 2010-specific code in there then you can just fix each error specifically or, if you don't know how, ask here.
i have a class that download and extract data, and i assign that to the multiple background worker, i had to place a SyncLock on the part that downloads data, this is because the the module that downloads data, there are local variables to that sub, so i fear another worker might overwrite these variables before the previous worker finish using it.
somthing lick this
vb Code:
sub dowork
dim spider as new webSpider
webspider.fetchdata()
end sub
now somewhere inside spider class in the download webpage module
vb Code:
SyncLock(url)
siteHtml=GetWebsiteHtml(url)
endSyncLock
the problem is that each worker thread must wait for the previous one to finish which defeats the purpose of using multiple worker threads in the first place, i was hoping there is a work around for this
Programming is all about good logic. Spend more time here
If there's a shared resource that each thread uses briefly and occasionally then that's OK, but if all threads are competing for a shared resource at all times then using multiple threads is pointless. It's hard to say for sure with so little information but it sounds like your class should be using an instance variable and then you should be creating one instance per thread. That way, each thread has its own variable and there's no competition.
I have a question about your BackgroundMultiWorker that I hope you can help me with.
I downloaded your .zip file and ran the demo successfully. It worked with no errors. I would like to use it in another project. So, I added the Wunnell.Threading project to one of my existing VS2010 VB.Net projects, created a reference to the Wunnell.Threading dll and proceeded to create the controls in my project that you had in your demo (e.g., DataGridView, buttons, etc.).
My question: When I the start my project and click it's "Start Theads" button, the I can watch the step-by-step execution of it through your worker (BackgroundMultiWorker). To make the debugging easier, I set the Thread count spinner to "1". The worker's DoWork event fires, but the ProgressChanged event never fires...and the the new row is never added to the DataGridView. When the ProcessCompleted event fires, an error is raised when the runningRowsByToken try's to retrieve the row from its Dictionary.
I think your demo is great, and would love to use it in my project. I've been looking at this issue all day and have not been able to figure it out. Do you have any advice for how I can get it working? I would really appreciate your help. Thanks.
One additional question...is it possible to pass more than just the token (GUID) to the DoWork event parameter of the worker? My project has a large datatable and my goal is to break it into multiple sub-tables (e.g., each one having 20 rows from the larger datatable). I would like to start each worker and pass it one of these sub-tables along with its token parameter. Then the DoWork event would perform the processing of the sub-table.
I have used the BackgroundWorker in other projects and know how to send multiple parameters with the RunWorkerAsync event. Can I also do this with the BackgroundMultiWorker?
If not, then I would probably look at creating a collection that would contain key/value pairs of tokens and sub-tables. I would have to manage this collection (adding the key/value pair with each workers' RunWorkerAsync event and removing key/value pairs with each worker's ProcessCompleted event, etc.).
I have a question about your BackgroundMultiWorker that I hope you can help me with.
I downloaded your .zip file and ran the demo successfully. It worked with no errors. I would like to use it in another project. So, I added the Wunnell.Threading project to one of my existing VS2010 VB.Net projects, created a reference to the Wunnell.Threading dll and proceeded to create the controls in my project that you had in your demo (e.g., DataGridView, buttons, etc.).
My question: When I the start my project and click it's "Start Theads" button, the I can watch the step-by-step execution of it through your worker (BackgroundMultiWorker). To make the debugging easier, I set the Thread count spinner to "1". The worker's DoWork event fires, but the ProgressChanged event never fires...and the the new row is never added to the DataGridView. When the ProcessCompleted event fires, an error is raised when the runningRowsByToken try's to retrieve the row from its Dictionary.
I think your demo is great, and would love to use it in my project. I've been looking at this issue all day and have not been able to figure it out. Do you have any advice for how I can get it working? I would really appreciate your help. Thanks.
Mark
Have you set the WorkerReportsProgress property of the BackgroundMultiWorker to True? Just like for the standard BackgroundWorker, that property is False by default. That's tripped me up a number of times with the BackgroundWorker so I could have set it to True by default but I wanted to mimic the BackgroundWorker as closely as possible.
If that doesn't alleviate the issue of the exception being thrown when RunWorkerCompleted is raised, please provide specifics of the exception.
One additional question...is it possible to pass more than just the token (GUID) to the DoWork event parameter of the worker? My project has a large datatable and my goal is to break it into multiple sub-tables (e.g., each one having 20 rows from the larger datatable). I would like to start each worker and pass it one of these sub-tables along with its token parameter. Then the DoWork event would perform the processing of the sub-table.
I have used the BackgroundWorker in other projects and know how to send multiple parameters with the RunWorkerAsync event. Can I also do this with the BackgroundMultiWorker?
If not, then I would probably look at creating a collection that would contain key/value pairs of tokens and sub-tables. I would have to manage this collection (adding the key/value pair with each workers' RunWorkerAsync event and removing key/value pairs with each worker's ProcessCompleted event, etc.).
Thanks again for any advice you can give me.
Mark
It works the same way as a regular BackgroundWorker but with the addition of the token. When you call RunWorkerAsync you can either pass no arguments or one object. That object can be of any type you like and can therefore be as complex as you like. It's completely up to the DoWork event handler to interpret and use that object.
The same goes for the BackgroundMultiWorker. When you call RunWorkerAsync you can pass just the token or the token and one object. That object can be anything you like and therefore as complex as you like and it's completely up to the DoWork event handler to interpret and use it.
Thank you so much for responding to my questions! I wasn't sure if you were still monitoring this thread since there haven't been any postings for more than a year.
I understand your comments to my second question. I'll work on that tomorrow.
As for my first question, I would like to provide some specifics. First off, there is no exception (error) when I click the start button. As I mentioned, I single-stepped through the code execution and saw that the ProcessChanged event simply never fires. I worked on this most of the day today and finally gave up trying to get it to work in my initial project. I began this project by creating a new VS2010 project in VB.Net. I added your demo Wunnell.Threading project as a second project to my solution, created a reference to the Wunnell.Threading.dll file, and then *created from scratch* a new form and each of the controls that you have in Form1 of your BackgroundMultiWorker Test project. So, after a lot of time convincing myself that I didn't know what I was doing, I gave up this approach and instead started a new project - but this time I added both your Wunnell.Threading project and your BackgroundMultiWorker Test project to my solution (it now only has those 2 projects). I created the Wunnell.Threading.dll reference. Everything compiled perfectly and your demo application worked in my new project. After I confirmed that the demo worked, then I went about modifying your Form1 and its code to work with my application. My summary...the first crack I took at creating a new project and trying to mimic your Form1 controls and code was not successful because something (or possible may things) weren't exactly duplicated. But I would still like to know what I did wrong (always on a "learning curve' and enjoy the journey). Any of my future projects that use the BackgroundMultiWorker would be nice to not have to go through the steps of adding the BackgroundMultiWorker Test project and then re-working the Form1 controls, code, etc. to fit my application.
One last question: Why is the _ReSharper.Wunnell.Threading folder's included with the files in your demo .zip file? What is this folder and it's files purpose?
John, I really appreciate that you have taken the time to respond to my questions and I've really gained a lot by using your BackgroundMultiWorker. If you'd like to see a screen shot of the application I've built with it, I'd be happy to e-mail it to you.
The only reason that I can think of that the ProgressChanged event wouldn't be raised is that the WorkerReportsProgress property was set to False. That property's entire reason for existing is to specify whether or not the ProgressChanged event is raised, so it's the obvious reason too. If you don't specifically remember setting that property to True then you probably didn't.
Originally Posted by Mark@SF
One last question: Why is the _ReSharper.Wunnell.Threading folder's included with the files in your demo .zip file? What is this folder and it's files purpose?
ReSharper is a plugin for VS that provides extended Intellisense, refactoring and more. Those folders are created by ReSharper to remember project-specific settings. I usually delete those when I upload a demo but I forgot in this case. If you don't have ReSharper installed then they will simply be ignored.
Its really great. I was just thinking if you can implement a function to pause it. Currently I am using a endless loop to pause a thread.
The simplest way to pause code is by calling Thread.Sleep. You don't want to do that on the UI thread though, because it will freeze the UI. The whole point of this class is to execute code on secondary threads though, so you can probably call Thread.Sleep with impunity in the DoWork event handler or any method called from it.
The simplest way to pause code is by calling Thread.Sleep. You don't want to do that on the UI thread though, because it will freeze the UI. The whole point of this class is to execute code on secondary threads though, so you can probably call Thread.Sleep with impunity in the DoWork event handler or any method called from it.
Currently What i have did is,
1. Declaring a shared boolean variable on Main Form. When pause is clicked, i change value there.
2. Adding a function to check if pause = true, if its true then starting a endless loop
like this.
While pause
end while
this is done in my worker class. so it doesnt hangs up UI. is this correct way?
Never Give UP! It is Mindset that brings you success!
No it's not. That is a "busy wait" loop and is one of the worst constructs in programming. The desire is to do nothing but it's actually running your processor at 100% capacity! By calling Thread.Sleep you actually do tell the thread to do nothing. The thing is, you want to be able to continue again when the user says so so you can't actually do absolutely nothing. The solution is a compromise, but skewed heavily towards the "nothing" end rather than the "everything" end.
Code:
While pause
Thread.Sleep(1000)
End While
Instead of constantly testing 'pause' like your original code, which will means thousands of times per second, That code tests 'pause' once per second. That means that there might be up to a second delay between the user clicking continue and the code actually continuing, but that's not really a big deal and will likely not even be noticed. If you want to reduce the potential delay then you can reduce the period of sleep. Even if the code tests twice, four times or even ten times per second, it's still going to be infinitely better than checking constantly.
No it's not. That is a "busy wait" loop and is one of the worst constructs in programming. The desire is to do nothing but it's actually running your processor at 100% capacity! By calling Thread.Sleep you actually do tell the thread to do nothing. The thing is, you want to be able to continue again when the user says so so you can't actually do absolutely nothing. The solution is a compromise, but skewed heavily towards the "nothing" end rather than the "everything" end.
Code:
While pause
Thread.Sleep(1000)
End While
Instead of constantly testing 'pause' like your original code, which will means thousands of times per second, That code tests 'pause' once per second. That means that there might be up to a second delay between the user clicking continue and the code actually continuing, but that's not really a big deal and will likely not even be noticed. If you want to reduce the potential delay then you can reduce the period of sleep. Even if the code tests twice, four times or even ten times per second, it's still going to be infinitely better than checking constantly.
Yes your correct. I will change it.
Never Give UP! It is Mindset that brings you success!
I have implemented your BackgroundMultiWorker in an application. It works great, thanks to you.
The application has a Tasks collection and each Task in the collection has a Status property ("Running", Cancelled", "Completed", or "Paused").
I would like to pause a running background task. My form has a Pause button. The approach that I'm considering to pause a task is as follows:
1. Click the Pause button which fires the button's Click event.
2. The button's Click event sets the Task's Status to "Paused" if the Status is "Running", or to "Running" if the Status is "Paused" (essentially a toggle of the Status).
In the BackgroundMultiWorker's DoWork event, I would handle pausing the Task by monitoring its Status. If the Status is "Paused", then I would enter a Do Loop with a System.Threading.Thread(1000) call to sleep for 1 second. Inside the loop I would test the Task's Status again and exit the loop if its Status is not "Paused". If the status is not "Paused", then the DoWork event proceeds a usual (looping through my database records and raising the BackgroundMultiWorker's ProgressChanged event with each loop).
My question for you is how can I raise the BackgroundMultiWorker's DoWork event in the Pause button's Click event (and pass it the Sender object and the Wunnell.Threading.DoWorkEventArgs)? Or, if I'm not on the right track, then what would be a better approach?
Can you help me with this? Would greatly appreciate your assistance.
Last edited by Mark@SF; Jan 29th, 2014 at 06:15 PM.
My question for you is how can I raise the BackgroundMultiWorker's DoWork event in the Pause button's Click event (and pass it the Sender object and the Wunnell.Threading.DoWorkEventArgs)?
I have the pause/restart feature working by adding a task status property to the Tasks collection and using it to pause/restart the task. I am still be interested (as a learning point) in how to raise the BackgroundMultiWorker's events from outside of the BackgroundMultiWorker.
I am still be interested (as a learning point) in how to raise the BackgroundMultiWorker's events from outside of the BackgroundMultiWorker.
You don't. You can't ever raise an object's events from outside. Only the object itself can raise its own event. The object might provide an interface to prompt it to do so, e.g. the Button.PerformClick method, but the raising of the event itself is still the responsibility of the object. The only way to raise a DoWork event on a BackgroundWorker is to call its RunWorkerAsync method. My BackgroundMultiWorker is modelled on the BackgroundWorker so the same rule applies.