串联执行并行任务包
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);
}
我有一个 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);
}