【问题标题】:Queue Multithreaded Methods for Execution队列多线程方法执行
【发布时间】:2012-07-30 06:42:55
【问题描述】:

所有,我有一个名为TaskSpin 的通用方法,在这个方法中我启动了一个Task 和一个关联的continutation

public TaskSpin(Func asyncMethod, object[] methodParameters)
{
    ...
    asyncTask = Task.Factory.StartNew<bool>(() => 
        asyncMethod(uiScheduler, methodParameters));

    asyncTask.ContinueWith(task =>
    {
        // Finish the processing update UI etc.
    }
    ...
}

现在的问题是我想使用TaskSpin 运行多个方法,但我需要限制这些方法一次运行一个。所以在一些DataGridView 中的foreach 行我想做类似的事情

foreach (DataGridViewRow row in this.DataGridViewUrg.Rows)
    TaskSpin(Run(DrgDataRowInfo(row.Index)));

但是,在上面的TaskSpin 方法将立即退出,导致TaskSpin 在另一个线程上分离下一个方法。这是不好的,因为Run 方法会写入一组通用文件。将这些作业排队的最佳方式是什么?

【问题讨论】:

  • 有没有理由不能通过将这些动作扔给调度程序来使用内置队列?
  • 我不知道,但我不熟悉这样做...感谢您的时间。

标签: c# winforms queue task-parallel-library


【解决方案1】:

您可以实现自己的任务队列,并在每个任务完成后继续处理队列,直到它为空,例如

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();           
    }  
    ...                 
}

【讨论】:

  • 是的,我非常喜欢这个。干杯。我打算使用ConncurrentCollection (BlockinCollction&lt;Action&gt;) 你能评论一下这种方法吗?感谢您的宝贵时间。
  • 否决这是荒谬的。事实。
  • @Killercam 您可以将Queue&lt;T&gt; 换成ConcurrentQueue&lt;T&gt;,因为您仍在技术上从不同的线程更新队列。但是,考虑到您没有同时修改它,我认为您应该没问题。
  • 只是一个问题:为什么不像我在回答中建议的那样使用延续链?它更简单。唯一的缺点是您不会引用所有排队的任务......但这是必要的吗?谢谢。
  • 因为这涉及到修改一个复杂的方法(TaskSpin),它不仅控制这个过程,还控制其他过程......谢谢你的帮助。
【解决方案2】:

您可以使用延续链实现任务“队列”。这很容易阅读和理解,并且可以按预期工作。此外,“排队”逻辑现在包含在您的 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.
    }
    ...
}

这将确保每个新任务仅在最后一个任务完成后运行。

编辑:如果您希望您的 UI 任务包含在顺序队列中,只需进行简单更改:

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.
    }
    ...
}

【讨论】:

    【解决方案3】:

    您可能应该考虑通过在继续之前锁定 Run 需要的对象来实现同步。你会像下面这样:

    // 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
        }
    }
    

    虽然,我很确定这不能保证任务按顺序完成。这听起来对你来说可能很重要,所以我的建议可能不是你想要的。

    【讨论】:

    • 如果每个任务都是按顺序处理的,那么应该不需要加锁。
    • 是的,但是延续链方法本质上是串行执行工作,并否定了使用任务并行库的好处。
    • 我试图指出的一点是,将工作本身强制序列化作为解决运行方法重入问题的方法并不是完成工作的最有效方式。
    【解决方案4】:

    如果您的目标是对 UI 进行序列化更新,则无论如何都必须这样做,因为 UI 只能从 GUI 线程更新。幸运的是,已经内置了执行此操作的机制。

    我注意到您正在使用 winforms。鉴于此,我建议使用SafeInvoke 之类的机制。这会将代码排队等待在 GUI 线程上执行。

    【讨论】:

    • 更新 uI 不是这里的问题 - 我的 TaskSpin 方法使用 TaskScheduler.FromCurrentSynchronizationContext() 处理这个问题。谢谢...
    猜你喜欢
    • 2014-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-27
    • 2013-10-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多