All, I have a generic method called TaskSpin
, in this method I launch a Task
with an ascociated continutation
public TaskSpin(Func asyncMethod, object[] methodParameters)
{
...
asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
asyncTask.ContinueWith(task =>
{
// Finish the processing update UI etc.
}
...
}
The problem is now that I want to run multiple methods using TaskSpin
, but I need to restrict the methods to run one-at-a-time. So foreach row in some DataGridView
I want to do something like
foreach (DataGridViewRow row in this.DataGridViewUrg.Rows)
TaskSpin(Run(DrgDataRowInfo(row.Index)));
However, in the above the TaskSpin
method will exit immediately causing TaskSpin
to spin off the next method on yet another thread. This is no good as the Run
method write to a common set of files. What is the best way to queue these jobs?
Thanks for your time.
You could implement your own task queue and just keep processing the queue after each task is complete until it's empty e.g.
using TaskPair = KeyValuePair<Func, object[]>;
...
private Queue<TaskPair> taskQueue;
...
// generate the queue of tasks
this.taskQueue = new Queue<TaskPair>(this.DataGridViewUrg.Rows);
foreach (DataGridViewRow row in this.DataGridViewUrg.Rows)
{
var task = new TaskPair(Run(DrgDataRowInfo(row.Index)), /* params */);
this.taskQueue.Enqueue(task);
}
// initiate queue processing
ProcessNextTask();
....
private void ProcessNextTask()
{
try
{
var item = this.taskQueue.Dequeue();
TaskSpin(item.Key, item.Value);
}
catch(InvalidOperationException)
{
// queue is empty
}
}
....
// Execute task and process next in queue (if applicable)
public TaskSpin(Func asyncMethod, object[] methodParameters)
{
...
asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
asyncTask.ContinueWith(task =>
{
// Finish the processing update UI etc.
ProcessNextTask();
}
...
}
You can implement a "queue" of tasks using a continuation chain. This is easy to read and understand and will work as expected. Also, the "queueing" logic is now contained within your TaskSpin.
private Task lastTask;
public void TaskSpin(Func asyncMethod, object[] methodParameters)
{
...
if(lastTask == null)
asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
else
asyncTask = lastTask.ContinueWith(t =>
asyncMethod(uiScheduler, methodParameters));
lastTask = asyncTask;
asyncTask.ContinueWith(task =>
{
// Finish the processing update UI etc.
}
...
}
This will ensure that each new Task will only run once the last Task has been completed.
Edit: If you want your UI task to be included in the sequential queue, a simple change:
private Task lastTask;
public void TaskSpin(Func asyncMethod, object[] methodParameters)
{
...
if(lastTask == null)
asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
else
asyncTask = lastTask.ContinueWith(t =>
asyncMethod(uiScheduler, methodParameters));
lastTask = asyncTask.ContinueWith(task =>
{
// Finish the processing update UI etc.
}
...
}
You should probably think about implementing synchronization by locking an object that Run requires before continuing. You would something like the following:
// locking object for write synchronization
object syncObj;
...
public TaskSpin(Func asyncMethod, object[] methodParameters)
{
...
asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
asyncTask.ContinueWith(task =>
{
lock(syncObj)
{
// Finish the processing update UI etc.
}
}
...
}
public void Run()
{
lock(syncObj)
{
// write results to common file
}
}
Although, I am pretty sure this will NOT guarantee that the tasks complete in order. This sounds like it might be important to you, so possibly my suggestion is not exactly what you want.
If your goal is to serialize updates to the UI, this has to happen anyway because UI can only be updated from the GUI thread. Fortunately, mechanisms to do this are already built in.
I note that you're using winforms. Given that, I suggest using a mechanism such as SafeInvoke. This will queue up code for execution on the GUI thread.