串联执行并行任务包

Execute Bundles of Parallel Tasks in Series

我有一个 GUI,允许用户对各种对象进行更改(其中包含一些内部规则),然后单击 保存 使用 EF6 将更改写入数据库。当他们对某些对象进行更改时,我想首先停用数据库中所有当前活动的对象(顺序并不重要),然后在完成后,我想插入新的活动对象(同样,在任意顺序)。

我想我可以通过制作两个任务列表来实现这一点——一个用于停用对象 (deactivate_Tasks),另一个用于添加新对象 (add_Tasks)。我认为我将能够 await Task.WhenAll(deactivate_Tasks) 以确保第一组任务并行完成,但与第二组任务串联,在下一行执行 (await Task.WhenAll(add_Tasks))。因此,只有在停用完成后才会进行添加。

但是,当我 运行 代码时,我得到了看似不稳定的结果,所有添加和停用任务都以不可预测的顺序发生。我不知道这是为什么,我想避免它 - 任何建议都非常受欢迎。

我在下面模拟了我的代码示例以提供帮助 - 希望方法名称是不言自明的,但如果不是,请询问。在实际情况下,需要添加和停用的规则类型更多。我正在使用 C# 4.8 并尽可能多地使用 MVVM 框架编写 windows WPF 应用程序。

public class MyViewModel
{
    private ICommand _saveCommand;
    public ICommand SaveCommand
    {
        get
        {
            return _saveCommand ?? (_saveCommand = new RelayCommand(execute => Save(), canExecute => IsDirty));
        }
        set
        { _saveCommand = value; }
    }
    private RuleDataService _ruleDataService { get; set; }

    public async void Save()
    {
        var add_Tasks = new List<Task<StatusCode>>();
        var deactivate_Tasks = new List<Task<StatusCode>>();

        if (CustomerRule_IsDirty)
        {
            add_Tasks.Add(_rulesDataService.CREATE_Rule(newCustomerRule));
            if (existingCustomerRule != null)
            {
                deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingCustomerRule));
            }
        }
        if (WarehouseRule_IsDirty)
        {
            add_Tasks.Add(_rulesDataService.CREATE_Rule(newWarehouseRule));
            if (existingWarehouseRule != null)
            {
                deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingWarehouseRule))
            }
        }

//MY COMMENTS REFLECT WHAT I HOPED TO ACHIEVE, NOT WHAT ACTUALLY HAPPENS
        //wait for all the deactivations to be done
        await Task.WhenAll(deactivate_Tasks).ConfigureAwait(false);
        //once everything is deactivated, add the replacements
        await Task.WhenAll(add_Tasks).ConfigureAwait(false);
    }
}

/// <summary>
/// Sample only - hopefully the methods names used above are self explanatory, but they all return a Task<StatusCode>
/// </summary>
public class RuleDataService
{
    public async Task<StatusCode> DEACTIVATE_Rule(Customer_Rule customer_Rule)
    {
        using(var context = new DBContext())
        {
            context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
            return await Task.Run(() =>
            {
                foreach (var existingRule in context.Customer_Rules.Where(r => r.CustomerName == customer_Rule.CustomerName && r.IsActive && r.RuleSetId != customer_Rule.RuleSetId))
                {
                    existingRule.IsActive = false;
                }
                context.SaveChanges();
                return StatusCode.TaskComplete;

            }).ConfigureAwait(false);
        }
    }

    public async Task<StatusCode> DEACTIVATE_Rule(Warehouse_Rule warehouse_Rule)
    {
        //basically the same as the other DEACTIVATE methods, just a different table
    }

    public async Task<StatusCode> CREATE_Rule(Customer_Rule customer_Rule)
    {
        //basically the same as the other DB methods, but performs an Add instead of an UPDATE
    }

    public async Task<StatusCode> CREATE_Rule(Warehouse_Rule warehouse_Rule)
    {
        //basically the same as the other DB methods, but performs an Add instead of an UPDATE
    }
}

我在谷歌上搜索了很多答案,但答案似乎只提供了关于如何 运行 我已经实现的一系列并行任务的建议,而不是如何捆绑在一起-系列并行动作集。

await 类似于“完成此任务后继续执行”。值得注意的是,它根本没有说明任务何时开始。因此,您的示例代码添加和停用任务都将 运行 并行,从而导致您描述的行为。解决方案是在停用任务之后启动添加任务。即

// Create all deactivate tasks
if (CustomerRule_IsDirty && existingCustomerRule != null)
{
    deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingCustomerRule));
}
if (WarehouseRule_IsDirty && existingWarehouseRule  != null)
{
    deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingWarehouseRule))
}

await Task.WhenAll(deactivate_Tasks).ConfigureAwait(false);

// Create all add tasks
if (CustomerRule_IsDirty)
{
     add_Tasks.Add(_rulesDataService.CREATE_Rule(newCustomerRule));
}
if (WarehouseRule_IsDirty)
{
     add_Tasks.Add(_rulesDataService.CREATE_Rule(newWarehouseRule));
}
await Task.WhenAll(add_Tasks).ConfigureAwait(false);

此外,您正在另一个线程上下文中创建 DbContext 对象,而不是在使用它的地方。我建议将创建和处置移至 Task.Run(...) 以避免任何潜在问题。

所有异步任务都是“热”创建的。换句话说,当您的代码调用 returns 任务的方法时,该方法调用就是 启动 任务的。返回时任务正在进行中。添加到列表时仍在进行中。

因此,如果您想延迟创建第二批,请先不要调用这些方法:

public async Task Save()
{
  var deactivate_Tasks = new List<Task<StatusCode>>();
  if (CustomerRule_IsDirty)
  {
    if (existingCustomerRule != null)
    {
      deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingCustomerRule));
    }
  }
  if (WarehouseRule_IsDirty)
  {
    if (existingWarehouseRule != null)
    {
      deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingWarehouseRule))
    }
  }
  await Task.WhenAll(deactivate_Tasks).ConfigureAwait(false);

  var add_Tasks = new List<Task<StatusCode>>();
  if (CustomerRule_IsDirty)
  {
    add_Tasks.Add(_rulesDataService.CREATE_Rule(newCustomerRule));
  }
  if (WarehouseRule_IsDirty)
  {
    add_Tasks.Add(_rulesDataService.CREATE_Rule(newWarehouseRule));
  }
  await Task.WhenAll(add_Tasks).ConfigureAwait(false);
}