Queue Mutithreaded Methods for Execution

2019-05-28 09:56发布

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.

4条回答
▲ chillily
2楼-- · 2019-05-28 10:31

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();           
    }  
    ...                 
}
查看更多
迷人小祖宗
3楼-- · 2019-05-28 10:35

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.

查看更多
淡お忘
4楼-- · 2019-05-28 10:37

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.
    }
    ...
}
查看更多
欢心
5楼-- · 2019-05-28 10:45

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.

查看更多
登录 后发表回答