问题
I need to create queue and use it with BackgroundWorker. So I can add operations and when one is done next is starting in background. I found this code by google:
public class QueuedBackgroundWorker<T>
{
public void QueueWorkItem(
Queue queue,
T inputArgument,
Func<T> doWork,
Action workerCompleted)
{
if (queue == null) throw new ArgumentNullException("queue");
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += (sender, args) =>
{
if (doWork != null)
{
args.Result = doWork(new DoWorkArgument<T>((T)args.Argument));
}
};
bw.RunWorkerCompleted += (sender, args) =>
{
if (workerCompleted != null)
{
workerCompleted(new WorkerResult<T>((T)args.Result, args.Error));
}
queue.Dequeue();
if (queue.Count > 0)
{
QueueItem<T> nextItem = queue.Peek() as QueueItem<T>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
};
queue.Enqueue(new QueueItem<T>(bw, inputArgument));
if (queue.Count == 1)
{
QueueItem<T> nextItem = queue.Peek() as QueueItem<T>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
}
}
public class DoWorkArgument<T>
{
public DoWorkArgument(T argument)
{
this.Argument = argument;
}
public T Argument { get; private set; }
}
public class WorkerResult<T>
{
public WorkerResult(T result, Exception error)
{
this.Result = result;
this.Error = error;
}
public T Result { get; private set; }
public Exception Error { get; private set; }
}
public class QueueItem<T>
{
public QueueItem(BackgroundWorker backgroundWorker, T argument)
{
this.BackgroundWorker = backgroundWorker;
this.Argument = argument;
}
public T Argument { get; private set; }
public BackgroundWorker BackgroundWorker { get; private set; }
}
But I have problem with doWork and workerCompleted. I get error:
Delegate 'Func' does not take 1 arguments
How can I fix this? How should I change parameters? Thanks
回答1:
Here's a much shorter method that does what you want:
public class BackgroundQueue
{
private Task previousTask = Task.FromResult(true);
private object key = new object();
public Task QueueTask(Action action)
{
lock (key)
{
previousTask = previousTask.ContinueWith(t => action()
, CancellationToken.None
, TaskContinuationOptions.None
, TaskScheduler.Default);
return previousTask;
}
}
public Task<T> QueueTask<T>(Func<T> work)
{
lock (key)
{
var task = previousTask.ContinueWith(t => work()
, CancellationToken.None
, TaskContinuationOptions.None
, TaskScheduler.Default);
previousTask = task;
return task;
}
}
}
By adding each new action as a continuation of the previous you ensure that only one is worked on at a time, as the next item won't start until the previous item is finished, you ensure that there is no thread sitting around idling when there is nothing to be worked on, and you ensure they're all done in order.
Also note that if you only ever think you'll need one queue, and not any number, you could make all of the members static
, but that's up to you.
回答2:
It seems you are missing the second generic parameter - Tout;
The following code should take care of it:
using System;
using System.Collections.Generic;
using System.ComponentModel;
public static class QueuedBackgroundWorker
{
public static void QueueWorkItem<Tin, Tout>(
Queue<QueueItem<Tin>> queue,
Tin inputArgument,
Func<DoWorkArgument<Tin>, Tout> doWork,
Action<WorkerResult<Tout>> workerCompleted)
{
if (queue == null) throw new ArgumentNullException("queue");
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += (sender, args) =>
{
if (doWork != null)
{
args.Result = doWork(new DoWorkArgument<Tin>((Tin)args.Argument));
}
};
bw.RunWorkerCompleted += (sender, args) =>
{
if (workerCompleted != null)
{
workerCompleted(new WorkerResult<Tout>((Tout)args.Result, args.Error));
}
queue.Dequeue();
if (queue.Count > 0)
{
QueueItem<Tin> nextItem = queue.Peek(); // as QueueItem<T>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
};
queue.Enqueue(new QueueItem<Tin>(bw, inputArgument));
if (queue.Count == 1)
{
QueueItem<Tin> nextItem = queue.Peek() as QueueItem<Tin>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
}
}
public class DoWorkArgument<T>
{
public DoWorkArgument(T argument)
{
this.Argument = argument;
}
public T Argument { get; private set; }
}
public class WorkerResult<T>
{
public WorkerResult(T result, Exception error)
{
this.Result = result;
this.Error = error;
}
public T Result { get; private set; }
public Exception Error { get; private set; }
}
public class QueueItem<T>
{
public QueueItem(BackgroundWorker backgroundWorker, T argument)
{
this.BackgroundWorker = backgroundWorker;
this.Argument = argument;
}
public T Argument { get; private set; }
public BackgroundWorker BackgroundWorker { get; private set; }
}
And the usage should be:
private readonly Queue<QueueItem<int>> _workerQueue = new Queue<QueueItem<int>>();
private int _workerId = 1;
[Test]
public void BackgroundTest()
{
QueuedBackgroundWorker.QueueWorkItem(
this._workerQueue,
this._workerId++,
args => // DoWork
{
var currentTaskId = args.Argument;
var now = DateTime.Now.ToLongTimeString();
var message = string.Format("DoWork thread started at '{0}': Task Number={1}", now, currentTaskId);
return new { WorkerId = currentTaskId, Message = message };
},
args => // RunWorkerCompleted
{
var currentWorkerId = args.Result.WorkerId;
var msg = args.Result.Message;
var now = DateTime.Now.ToShortTimeString();
var completeMessage = string.Format(
"RunWorkerCompleted completed at '{0}'; for Task Number={1}, DoWork Message={2}",
now,
currentWorkerId,
msg);
}
);
}
来源:https://stackoverflow.com/questions/15119974/creating-backgroundworker-with-queue