I have a class WorkQueue, just take note about the DoNext() for now, the rest are mainly helpers. Basically, WorkQueue is just a Queue of WorkItems. DoNext() is responsible for “Starting a Pending WorkItem with a Free Background Worker”. Also note that it will set the WorkItem.Worker property.
public class WorkQueue<Tin, Tout> :
INotifyCollectionChanged, IEnumerable<WorkItem<Tin, Tout>>
{
public bool DoNext()
{
// check if any work left in queue
WorkItem<Tin, Tout> item = GetWork();
if (item != null)
{
// check if any free workers
BackgroundWorker worker = GetWorker();
Debug.WriteLine(
"[WorkQueue.DoNext] Setting Worker to WorkItem: " + worker);
item.Worker = worker;
if (worker != null)
{
worker.RunWorkerAsync(item);
return true;
}
}
return false;
}
public void AddWork(WorkItem<Tin, Tout> item)
{
_queue.Add(item);
RaiseCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item));
}
public WorkItem<Tin, Tout> GetWork()
{
return (from i in _queue
where i.Status == WorkStatus.Pending
select i).FirstOrDefault();;
}
public BackgroundWorker GetWorker()
{
return (from worker in _workers
where worker.IsBusy == false
select worker).FirstOrDefault();
}
}
The problem I am having is when I do something like below,
foreach (string filename in fileNames) {
UploadQueue.AddWork(new WorkItem<string, UploadedImage>(filename));
UploadQueue.DoNext();
}
Where UploadQueue is a WorkQueue<string, UploadedImage>. On the 1st (first only) DoNext(), the WorkItem.Worker is null. I know that because my Cancel button bound to WorkItem.CancelCommand is disabled. When debugging, I discovered the reason was because worker is null.
_cancelCommand = new RelayCommand(... () =>
{
// Returns true if WorkItem is being processed with a worker that supports
// cancellation or if the WorkItem is still Pending
// False if otherwise, eg. already completed, cancelled etc
if (Status == WorkStatus.Processing)
{
if (_worker != null && _worker.WorkerSupportsCancellation)
return true;
} else if (Status == WorkStatus.Pending) {
return true;
}
return false;
});
The solution is to move the DoNext() out of the loop,
foreach (string filename in fileNames)
UploadQueue.AddWork(new WorkItem<string, UploadedImage>(filename));
UploadQueue.DoNext();
but whats the problem with it inside, why is worker set to null? If its null, from the if clause, the BackgroundWorker should not start?
if (worker != null)
worker.RunWorkerAsync(item);
if all of the workers are busy, this function will return null;
UploadQueue.DoNext() executed multiple times
UploadQueue.DoNext() executed once.
Then it’s quite clear, if you execute UploadQueue.DoNext() multiple times in a short time period, there will be no worker which is not busy, so you get a null worker