基于 BufferBlock<T>
和 ActionBlock<T>
的用户下单、订单分配给智能炒菜机任务的解决方案代码框架
- 如果所用的平台支持 TPL 数据流,我建议大家使用
BufferBlock<T>
的方案。可惜并不是所有平台都支持 TPL 数据流。如果平台不支持BufferBlock<T>
,那可以用 NuGet 包 Nito.AsyncEx 中的AsyncProducerConsumerQueue<T>
类。它的 API 与BufferBlock<T>
类似,也可以实现类似的生产者 - 消费者模式。 BufferBlock<T>
是 TPL 数据流(Task Parallel Library Dataflow,简称 TPL Dataflow)中的一个组件,主要用于在生产者和消费者之间缓冲数据。它提供了线程安全的数据传递机制,可以方便地实现异步数据流处理。而 Nito.AsyncEx 库中的AsyncProducerConsumerQueue<T>
也是一个异步的生产者 - 消费者队列,它可以在不支持 TPL 数据流的平台上提供类似的异步数据传递功能。
两种方案都适用于需要在多线程环境中协调生产者和消费者操作的场景,例如在高并发应用、数据处理管道等场景下。它们都可以帮助开发者以更简洁和高效的方式处理多线程数据传递问题,避免因线程同步等问题导致的复杂性和潜在错误。
在实际选择时,如果平台支持 TPL 数据流,优先选择 BufferBlock<T>
,因为它是微软提供的官方组件,经过了广泛的测试和优化,具有良好的性能和稳定性。如果不支持 TPL 数据流,那么使用 Nito.AsyncEx 库中的 AsyncProducerConsumerQueue<T>
是一个很好的替代方案,它同样提供了强大的功能和较好的性能表现。
1. 引入必要的命名空间和 NuGet 包
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
确保项目中已安装 Microsoft.Tpl.Dataflow
NuGet 包。
2. 定义订单类和智能炒菜机类
// 订单类
public class Order
{
public int OrderId { get; set; }
public string DishName { get; set; }
public DateTime OrderTime { get; set; }
public int EstimatedCookingTimeInSeconds { get; set; } // 估计烹饪时间(秒)
}
// 智能炒菜机类
public class SmartCookingMachine
{
public string MachineId { get; set; }
public bool IsBusy { get; private set; }
public async Task CookAsync(Order order)
{
Console.WriteLine($"{DateTime.Now}: 炒菜机 {MachineId} 开始烹饪订单 {order.OrderId} ({order.DishName}),预计耗时 {order.EstimatedCookingTimeInSeconds} 秒");
IsBusy = true;
await Task.Delay(TimeSpan.FromSeconds(order.EstimatedCookingTimeInSeconds)); // 模拟烹饪过程
IsBusy = false;
Console.WriteLine($"{DateTime.Now}: 炒菜机 {MachineId} 完成烹饪订单 {order.OrderId} ({order.DishName})");
}
}
3. 创建订单处理管道
public class OrderProcessingSystem
{
private BufferBlock<Order> _orderBufferBlock;
private ActionBlock<Order> _orderProcessingBlock;
private List<SmartCookingMachine> _cookingMachines;
public OrderProcessingSystem(List<SmartCookingMachine> cookingMachines)
{
_cookingMachines = cookingMachines;
// 初始化 BufferBlock,用于缓冲订单
_orderBufferBlock = new BufferBlock<Order>();
// 初始化 ActionBlock,用于处理订单分配
_orderProcessingBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount // 根据CPU核心数设置并行度
});
// 将 BufferBlock 链接到 ActionBlock
_orderBufferBlock.LinkTo(_orderProcessingBlock, new DataflowLinkOptions { PropagateCompletion = true });
}
// 接收订单
public void ReceiveOrder(Order order)
{
_orderBufferBlock.Post(order);
}
// 处理订单分配
private async Task ProcessOrderAsync(Order order)
{
SmartCookingMachine availableMachine = null;
// 查找空闲的智能炒菜机
while (availableMachine == null)
{
availableMachine = _cookingMachines.FirstOrDefault(m => !m.IsBusy);
if (availableMachine == null)
{
Console.WriteLine($"{DateTime.Now}: 暂无空闲炒菜机,订单 {order.OrderId} 等待分配");
await Task.Delay(TimeSpan.FromSeconds(1)); // 短暂等待后重试
}
}
// 分配订单给智能炒菜机
await availableMachine.CookAsync(order);
}
// 完成订单处理
public void CompleteProcessing()
{
_orderBufferBlock.Complete();
_orderProcessingBlock.Complete();
Task.WhenAll(_orderProcessingBlock.Completion).Wait();
}
}
4. 示例使用代码
class Program
{
static async Task Main(string[] args)
{
// 创建智能炒菜机
var cookingMachines = new List<SmartCookingMachine>
{
new SmartCookingMachine { MachineId = "Machine1" },
new SmartCookingMachine { MachineId = "Machine2" },
new SmartCookingMachine { MachineId = "Machine3" }
};
// 初始化订单处理系统
var orderProcessingSystem = new OrderProcessingSystem(cookingMachines);
// 模拟用户下单
Console.WriteLine("开始模拟用户下单:");
for (int i = 1; i <= 10; i++)
{
var order = new Order
{
OrderId = i,
DishName = $"Dish_{i}",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = new Random().Next(5, 15) // 随机生成5-14秒的估计烹饪时间
};
orderProcessingSystem.ReceiveOrder(order);
Console.WriteLine($"订单 {order.OrderId} 已提交,预计烹饪时间:{order.EstimatedCookingTimeInSeconds} 秒");
await Task.Delay(TimeSpan.FromSeconds(2)); // 模拟每隔2秒提交一个订单
}
// 完成订单处理
orderProcessingSystem.CompleteProcessing();
Console.WriteLine("所有订单处理完成!");
Console.ReadKey();
}
}
代码框架说明:
-
订单接收与缓冲 :
BufferBlock<Order>
用于接收和缓冲用户下单产生的订单。它提供了线程安全的数据存储,确保订单能够被安全地传递到后续处理阶段。 -
订单分配与处理 :
ActionBlock<Order>
负责从BufferBlock<Order>
中获取订单,并将订单分配给空闲的智能炒菜机进行烹饪。它通过ProcessOrderAsync
方法实现订单分配逻辑,该方法会查找空闲的智能炒菜机并调用其CookAsync
方法进行烹饪。 -
智能炒菜机模拟 :
SmartCookingMachine
类模拟了智能炒菜机的工作状态。每个炒菜机在烹饪时会标记为忙碌状态,烹饪完成后恢复为空闲状态。通过这种方式,可以实现多个炒菜机的并行工作,提高订单处理效率。 -
并行处理与扩展性 :通过设置
ActionBlock<Order>
的MaxDegreeOfParallelism
属性,可以根据 CPU 核心数来控制并行处理的订单数量,从而充分利用系统资源。这种设计使得系统具有良好的扩展性,能够适应不同规模的订单处理需求。 -
任务完成通知 :在完成所有订单处理后,调用
CompleteProcessing
方法来通知数据流块完成处理,并等待所有任务完成,确保所有订单都得到处理。
这个框架可以根据实际需求进行扩展和优化,例如添加更多的错误处理逻辑、订单优先级处理、智能炒菜机的动态管理等功能,以满足更复杂的业务场景需求。
避免订单重复
在这种情况下,可以通过引入一个订单编号的集合来跟踪已经获取过的订单编号,从而避免重复获取相同编号的订单加入到
BufferBlock<Order>
。以下是修改后的代码框架,添加了订单编号去重的逻辑:
1. 修改订单接收逻辑
在订单接收方法中,添加一个用于记录已接收订单编号的集合,并在接收新订单时进行检查,以避免重复。
public class OrderProcessingSystem
{
private BufferBlock<Order> _orderBufferBlock;
private ActionBlock<Order> _orderProcessingBlock;
private List<SmartCookingMachine> _cookingMachines;
private HashSet<int> _receivedOrderIds; // 用于记录已接收的订单编号
public OrderProcessingSystem(List<SmartCookingMachine> cookingMachines)
{
_cookingMachines = cookingMachines;
_receivedOrderIds = new HashSet<int>();
// 初始化 BufferBlock,用于缓冲订单
_orderBufferBlock = new BufferBlock<Order>();
// 初始化 ActionBlock,用于处理订单分配
_orderProcessingBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount // 根据CPU核心数设置并行度
});
// 将 BufferBlock 链接到 ActionBlock
_orderBufferBlock.LinkTo(_orderProcessingBlock, new DataflowLinkOptions { PropagateCompletion = true });
}
// 接收订单
public bool ReceiveOrder(Order order)
{
if (_receivedOrderIds.Contains(order.OrderId))
{
Console.WriteLine($"订单 {order.OrderId} 已经存在,跳过重复订单");
return false;
}
_receivedOrderIds.Add(order.OrderId);
_orderBufferBlock.Post(order);
Console.WriteLine($"订单 {order.OrderId} 已接收并添加到缓冲区");
return true;
}
// 处理订单分配
private async Task ProcessOrderAsync(Order order)
{
SmartCookingMachine availableMachine = null;
// 查找空闲的智能炒菜机
while (availableMachine == null)
{
availableMachine = _cookingMachines.FirstOrDefault(m => !m.IsBusy);
if (availableMachine == null)
{
Console.WriteLine($"{DateTime.Now}: 暂无空闲炒菜机,订单 {order.OrderId} 等待分配");
await Task.Delay(TimeSpan.FromSeconds(1)); // 短暂等待后重试
}
}
// 分配订单给智能炒菜机
await availableMachine.CookAsync(order);
}
// 完成订单处理
public void CompleteProcessing()
{
_orderBufferBlock.Complete();
_orderProcessingBlock.Complete();
Task.WhenAll(_orderProcessingBlock.Completion).Wait();
}
}
2. 定时从服务器 API 获取订单
在定时从服务器获取订单时,调用 ReceiveOrder
方法来接收订单,该方法会自动检查订单编号是否已经存在。
class Program
{
static async Task Main(string[] args)
{
// 创建智能炒菜机
var cookingMachines = new List<SmartCookingMachine>
{
new SmartCookingMachine { MachineId = "Machine1" },
new SmartCookingMachine { MachineId = "Machine2" },
new SmartCookingMachine { MachineId = "Machine3" }
};
// 初始化订单处理系统
var orderProcessingSystem = new OrderProcessingSystem(cookingMachines);
// 启动定时任务从服务器获取订单
var cancellationTokenSource = new CancellationTokenSource();
Task.Run(async () =>
{
while (!cancellationTokenSource.Token.IsCancellationRequested)
{
await FetchOrdersFromServerAsync(orderProcessingSystem);
await Task.Delay(TimeSpan.FromSeconds(5), cancellationTokenSource.Token); // 每5秒获取一次订单
}
}, cancellationTokenSource.Token);
// 模拟程序运行一段时间
Console.WriteLine("开始运行订单处理系统...");
await Task.Delay(TimeSpan.FromSeconds(30));
// 完成订单处理
cancellationTokenSource.Cancel();
orderProcessingSystem.CompleteProcessing();
Console.WriteLine("所有订单处理完成!");
Console.ReadKey();
}
// 模拟从服务器获取订单
private static async Task FetchOrdersFromServerAsync(OrderProcessingSystem orderProcessingSystem)
{
Console.WriteLine($"{DateTime.Now}: 从服务器获取订单...");
// 模拟从服务器获取订单的逻辑
var newOrders = await GetOrdersFromServerAsync();
// 尝试接收每个新订单
foreach (var order in newOrders)
{
orderProcessingSystem.ReceiveOrder(order);
}
}
// 模拟从服务器获取订单的 API 调用
private static async Task<List<Order>> GetOrdersFromServerAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1)); // 模拟网络请求延迟
// 模拟返回一些新订单
return new List<Order>
{
new Order
{
OrderId = 1,
DishName = "Dish_1",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = 5
},
new Order
{
OrderId = 2,
DishName = "Dish_2",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = 8
},
new Order
{
OrderId = 3,
DishName = "Dish_3",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = 10
}
};
}
}
3. 代码框架说明
-
订单编号去重 :在
OrderProcessingSystem
类中添加了一个_receivedOrderIds
集合,用于存储已经接收的订单编号。在ReceiveOrder
方法中,每次接收新订单时都会检查该订单编号是否已经存在。如果存在,则跳过重复订单;否则,将订单编号添加到集合中,并将订单添加到BufferBlock<Order>
。 -
定时获取订单 :在
Program
类的Main
方法中,启动了一个定时任务,每隔 5 秒从服务器获取订单。调用FetchOrdersFromServerAsync
方法从服务器获取订单,并尝试将每个新订单通过ReceiveOrder
方法接收。 -
模拟服务器订单 :
GetOrdersFromServerAsync
方法模拟了从服务器获取订单的逻辑,返回一些示例订单。可以根据实际应用需求替换为真实的服务器 API 调用。 -
程序运行与完成 :在模拟程序运行一段时间后(30 秒),取消定时任务并完成订单处理,确保所有订单都得到处理。
通过这种设计,可以有效地避免从服务器获取到相同编号的重复订单,并将其加入到 BufferBlock<Order>
中。这样可以确保每个订单只处理一次,提高系统的可靠性和准确性。
当然可以。以下是订单处理流程的详细说明,涵盖了从订单接收、缓冲、分配到最终处理完成的整个过程:
1. 订单接收
- 从服务器获取订单 :通过定时任务(如每 5 秒一次),调用
FetchOrdersFromServerAsync
方法从服务器获取新订单。 - 检查订单编号 :在
ReceiveOrder
方法中,检查新订单的编号是否已经存在于_receivedOrderIds
集合中。- 如果订单编号已存在,跳过该订单,避免重复处理。
- 如果订单编号不存在,将该订单编号添加到
_receivedOrderIds
集合中,并将订单添加到BufferBlock<Order>
。
2. 订单缓冲
- 存储到 BufferBlock :新订单被添加到
BufferBlock<Order>
中,这是一个线程安全的缓冲区,用于暂存订单。 - 等待处理 :订单在
BufferBlock<Order>
中等待被进一步处理。
3. 订单分配
- 从 BufferBlock 获取订单 :
ActionBlock<Order>
从BufferBlock<Order>
中获取订单。 - 查找空闲炒菜机 :在
ProcessOrderAsync
方法中,查找空闲的智能炒菜机。- 如果找到空闲炒菜机,将订单分配给该炒菜机。
- 如果没有空闲炒菜机,短暂等待后重试。
4. 订单处理
- 烹饪过程 :智能炒菜机开始烹饪订单,模拟烹饪过程。
- 更新状态 :烹饪完成后,智能炒菜机的状态更新为空闲。
5. 完成订单处理
- 完成所有订单 :当所有订单都处理完成后,调用
CompleteProcessing
方法通知数据流块完成处理。 - 等待任务完成 :等待所有任务完成,确保所有订单都得到处理。
以下是订单处理流程的示意图:
graph TD
A[定时从服务器获取订单] --> B{订单编号是否已存在}
B -->|是| C[跳过重复订单]
B -->|否| D[将订单编号添加到集合]
D --> E[将订单添加到 BufferBlock<Order>]
E --> F[订单在 BufferBlock<Order> 中等待]
F --> G[ActionBlock<Order> 从 BufferBlock<Order> 获取订单]
G --> H[查找空闲智能炒菜机]
H -->|找到| I[分配订单给智能炒菜机]
I --> J[智能炒菜机开始烹饪]
J --> K[烹饪完成,更新状态]
H -->|未找到| L[短暂等待后重试]
K --> M[订单处理完成]
M --> N[通知数据流块完成处理]
N --> O[等待所有任务完成]
通过这种流程,可以确保每个订单只处理一次,并且订单能够被高效地分配给空闲的智能炒菜机进行烹饪。这种设计提高了系统的可靠性和效率,避免了重复订单的处理问题。
BufferBlock 与其他线程安全队列(如 ConcurrentQueue、BlockingCollection 等)相比,具有以下不同之处:
BufferBlock<Order>
是线程安全的,它通过 TPL 数据流库的内部机制确保了多线程环境下的操作安全。以下是 BufferBlock<Order>
保证线程安全的主要方式:
1. 内部同步机制
BufferBlock<Order>
在内部使用了同步机制来协调多个线程对缓冲区的访问。当多个线程同时尝试向缓冲区添加或从中获取数据时,这些操作是通过内部的锁定或信号量等同步原语来协调的,确保不会出现竞态条件。
2. 线程安全的 API 设计
Post
方法 :用于向缓冲区添加数据。无论有多少个线程同时调用Post
方法,BufferBlock<Order>
都能确保每个数据项被正确地添加到缓冲区中,并且不会出现数据损坏或丢失的情况。TryReceive
方法 :用于从缓冲区获取数据。它也具有线程安全性,能够确保在多线程环境下,数据能够被正确地取出而不会出现重复获取或数据不一致的问题。LinkTo
方法 :用于将BufferBlock<Order>
链接到其他数据流块(如ActionBlock<Order>
)。在链接过程中,数据的传递也是线程安全的,确保数据能够正确地从一个块传递到另一个块。
3. 数据流块的组合与协调
- TPL 数据流库设计了数据流块之间的组合与协调机制。在将
BufferBlock<Order>
链接到其他数据流块(如ActionBlock<Order>
)时,数据流库会管理这些块之间的数据传递,确保数据在不同块之间的流动是线程安全的。
4. 其他线程安全特性
Complete
方法 :用于通知BufferBlock<Order>
不再接收新的数据。当调用Complete
方法后,BufferBlock<Order>
会进入完成状态,之后的Post
操作将被忽略,而TryReceive
操作会继续直到缓冲区为空。- 取消机制 :支持与
CancellationToken
一起使用,允许在需要时取消数据流操作。这有助于在多线程环境中更好地控制资源和避免死锁。
与其他线程安全结构的对比
- 与
lock
关键字相比 :BufferBlock<Order>
的线程安全机制更加高级和自动化。在使用lock
关键字时,开发者需要手动管理锁的获取和释放,并且需要确保在所有可能的代码路径中都正确地释放锁。而BufferBlock<Order>
在内部自动处理了这些同步细节,使得开发者可以更方便地编写线程安全的代码。 - 与
ConcurrentQueue<T>
相比 :两者都提供了线程安全的队列功能,但BufferBlock<Order>
是 TPL 数据流的一部分,它不仅提供了线程安全的存储,还提供了与其他数据流块(如ActionBlock<Order>
)进行组合和协调的能力,从而能够构建更复杂的数据处理管道。
BufferBlock<Order>
的线程安全设计使得它在多线程环境下能够可靠地工作,开发者可以放心地在并发场景中使用它来缓冲和传递数据。通过与其他 TPL 数据流块的组合,可以构建出强大而高效的异步数据处理流程。
设置 BufferBlock<T>
的缓冲策略主要通过其构造函数的参数或其属性来实现。
以下是一些常见的缓冲策略设置方法:
1. 构造函数参数设置
在创建 BufferBlock<T>
时,可以通过传递一个 DataflowBlockOptions
对象来设置缓冲策略。
var bufferBlock = new BufferBlock<Order>(new DataflowBlockOptions
{
BoundedCapacity = 10, // 设置缓冲区的最大容量为10
Greedy = true // 设置为贪婪模式,立即接收所有可用数据
});
BoundedCapacity
属性 :用于设置缓冲区的最大容量。如果缓冲区已满,后续的Post
操作将被阻塞,直到缓冲区中有空间可用。Greedy
属性 :用于设置缓冲区是否立即接收所有可用数据。如果设置为true
,缓冲区会尽可能快地接收数据;如果设置为false
,缓冲区会等待下游数据流块准备好后再接收数据。
2. 属性设置
在创建 BufferBlock<T>
后,可以通过其属性来设置缓冲策略。
var bufferBlock = new BufferBlock<Order>();
bufferBlock.BoundedCapacity = 10; // 设置缓冲区的最大容量为10
BoundedCapacity
属性 :用于设置缓冲区的最大容量。如果缓冲区已满,后续的Post
操作将被阻塞,直到缓冲区中有空间可用。
3. 使用场景
- 设置缓冲区容量限制 :当需要限制缓冲区中能够存储的最大数据量时,可以通过设置
BoundedCapacity
属性来实现。例如,在内存有限的环境中,或者需要避免缓冲区占用过多内存时,可以设置一个合适的缓冲区容量。 - 设置数据接收模式 :通过设置
Greedy
属性,可以控制缓冲区的数据接收模式。如果需要缓冲区尽可能快地接收数据,可以将Greedy
设置为true
;如果需要缓冲区等待下游数据流块准备好后再接收数据,可以将Greedy
设置为false
。
4. 示例代码
以下是一个完整的示例,展示如何设置 BufferBlock<Order>
的缓冲策略:
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
public class Order
{
public int OrderId { get; set; }
public string DishName { get; set; }
public DateTime OrderTime { get; set; }
public int EstimatedCookingTimeInSeconds { get; set; }
}
public class OrderProcessingSystem
{
private BufferBlock<Order> _orderBufferBlock;
public OrderProcessingSystem()
{
// 设置 BufferBlock<Order> 的缓冲策略
var options = new DataflowBlockOptions
{
BoundedCapacity = 10, // 设置缓冲区的最大容量为10
Greedy = true // 设置为贪婪模式,立即接收所有可用数据
};
_orderBufferBlock = new BufferBlock<Order>(options);
// 连接到其他数据流块(例如 ActionBlock<Order>)
var actionBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
});
_orderBufferBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
}
private async Task ProcessOrderAsync(Order order)
{
// 订单处理逻辑
Console.WriteLine($"处理订单 {order.OrderId} ({order.DishName})");
await Task.Delay(TimeSpan.FromSeconds(order.EstimatedCookingTimeInSeconds));
Console.WriteLine($"订单 {order.OrderId} 处理完成");
}
public void ReceiveOrder(Order order)
{
_orderBufferBlock.Post(order);
}
public void CompleteProcessing()
{
_orderBufferBlock.Complete();
}
}
class Program
{
static void Main(string[] args)
{
var orderProcessingSystem = new OrderProcessingSystem();
// 模拟接收订单
for (int i = 1; i <= 15; i++)
{
var order = new Order
{
OrderId = i,
DishName = $"Dish_{i}",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = new Random().Next(1, 5)
};
orderProcessingSystem.ReceiveOrder(order);
Console.WriteLine($"订单 {order.OrderId} 已提交");
Task.Delay(TimeSpan.FromSeconds(0.5)).Wait();
}
// 等待所有订单处理完成
orderProcessingSystem.CompleteProcessing();
Task.Delay(TimeSpan.FromSeconds(10)).Wait();
}
}
在这个示例中,BufferBlock<Order>
的缓冲策略通过构造函数的参数进行设置,设置了缓冲区的最大容量为 10,并开启了贪婪模式。通过这种方式,可以更好地控制缓冲区的行为,以适应不同的应用场景和需求。
ActionBlock<T>
是 TPL 数据流中的一个核心组件,用于执行某种操作
ActionBlock<T>
是 TPL 数据流中的一个核心组件,用于执行某种操作。它内部维护一个任务队列,可以将任务异步地添加到队列中进行处理。以下是一些常见的配置和使用方法。
1. 基本使用
var actionBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
});
- 这里创建了一个
ActionBlock<Order>
,它会异步地处理每个Order
对象。 ProcessOrderAsync
是一个异步方法,用于处理订单。
2. 设置最大并行度
通过设置 MaxDegreeOfParallelism
属性,可以指定 ActionBlock<T>
同时处理的最大任务数。
var actionBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 5 // 设置最大并行度为5
});
- 在这个例子中,
ActionBlock<Order>
最多会同时处理 5 个订单。 - 这可以帮助控制系统的并发级别,避免资源过度消耗。
3. 设置任务执行顺序
默认情况下,ActionBlock<T>
按照任务添加的顺序来处理任务。如果需要更改这个顺序,可以通过设置 EnsureOrdered
属性。
var actionBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, new ExecutionDataflowBlockOptions
{
EnsureOrdered = false // 设置任务执行顺序不保证
});
- 当
EnsureOrdered
设置为false
时,任务可能会按照与添加顺序不同的顺序来执行,这可能提高性能,特别是在高并发场景下。 - 默认情况下,
EnsureOrdered
属性为true
,即任务按照添加顺序执行。
4. 使用场景
- 并行任务处理 :当需要同时处理多个任务时,可以通过设置
MaxDegreeOfParallelism
属性来提高处理效率。 - 任务顺序控制 :根据业务需求,可以选择是否保证任务的执行顺序。
5. 示例代码
以下是一个完整的示例,展示如何使用 ActionBlock<Order>
:
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
public class Order
{
public int OrderId { get; set; }
public string DishName { get; set; }
public DateTime OrderTime { get; set; }
public int EstimatedCookingTimeInSeconds { get; set; }
}
public class OrderProcessingSystem
{
private BufferBlock<Order> _orderBufferBlock;
private ActionBlock<Order> _orderProcessingBlock;
public OrderProcessingSystem()
{
// 设置 BufferBlock<Order> 的缓冲策略
var bufferBlockOptions = new DataflowBlockOptions
{
BoundedCapacity = 10,
Greedy = true
};
_orderBufferBlock = new BufferBlock<Order>(bufferBlockOptions);
// 设置 ActionBlock<Order> 的执行策略
var actionBlockOptions = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 5,
EnsureOrdered = false
};
_orderProcessingBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, actionBlockOptions);
// 将 BufferBlock 链接到 ActionBlock
_orderBufferBlock.LinkTo(_orderProcessingBlock, new DataflowLinkOptions { PropagateCompletion = true });
}
private async Task ProcessOrderAsync(Order order)
{
Console.WriteLine($"开始处理订单 {order.OrderId} ({order.DishName})");
await Task.Delay(TimeSpan.FromSeconds(order.EstimatedCookingTimeInSeconds));
Console.WriteLine($"订单 {order.OrderId} 处理完成");
}
public void ReceiveOrder(Order order)
{
_orderBufferBlock.Post(order);
}
public void CompleteProcessing()
{
_orderBufferBlock.Complete();
_orderProcessingBlock.Complete();
Task.WhenAll(_orderProcessingBlock.Completion).Wait();
}
}
class Program
{
static void Main(string[] args)
{
var orderProcessingSystem = new OrderProcessingSystem();
// 模拟接收订单
for (int i = 1; i <= 15; i++)
{
var order = new Order
{
OrderId = i,
DishName = $"Dish_{i}",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = new Random().Next(1, 5)
};
orderProcessingSystem.ReceiveOrder(order);
Console.WriteLine($"订单 {order.OrderId} 已提交");
Task.Delay(TimeSpan.FromSeconds(0.5)).Wait();
}
// 等待所有订单处理完成
orderProcessingSystem.CompleteProcessing();
}
}
在这个示例中,ActionBlock<Order>
被配置为最大并行度为 5,并且不保证任务的执行顺序。通过这种方式,可以提高订单处理的效率。
一个完整的 ActionBlock
示例代码
它演示了如何使用 ActionBlock
来处理一组订单。这个示例代码将创建一个 ActionBlock<Order>
,将订单添加到它,并处理这些订单。
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
public class Order
{
public int OrderId { get; set; }
public string DishName { get; set; }
public DateTime OrderTime { get; set; }
public int EstimatedCookingTimeInSeconds { get; set; }
}
public class OrderProcessingSystem
{
private ActionBlock<Order> _orderProcessingBlock;
public OrderProcessingSystem()
{
// 设置 ActionBlock<Order> 的执行策略
var actionBlockOptions = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 5, // 最大并行度为5
EnsureOrdered = false // 不保证任务执行顺序
};
// 创建 ActionBlock<Order>
_orderProcessingBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, actionBlockOptions);
}
private async Task ProcessOrderAsync(Order order)
{
Console.WriteLine($"开始处理订单 {order.OrderId} ({order.DishName}),预计耗时 {order.EstimatedCookingTimeInSeconds} 秒");
await Task.Delay(TimeSpan.FromSeconds(order.EstimatedCookingTimeInSeconds)); // 模拟处理时间
Console.WriteLine($"订单 {order.OrderId} 处理完成");
}
public void AddOrder(Order order)
{
// 将订单添加到 ActionBlock<Order> 的输入队列中
_orderProcessingBlock.Post(order);
}
public void CompleteProcessing()
{
// 标记 ActionBlock<Order> 为完成状态,不再接受新的订单
_orderProcessingBlock.Complete();
// 等待所有订单处理完成
_orderProcessingBlock.Completion.Wait();
}
}
class Program
{
static void Main(string[] args)
{
var orderProcessingSystem = new OrderProcessingSystem();
// 模拟接收订单
for (int i = 1; i <= 10; i++)
{
var order = new Order
{
OrderId = i,
DishName = $"Dish_{i}",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = new Random().Next(1, 5)
};
orderProcessingSystem.AddOrder(order);
Console.WriteLine($"订单 {order.OrderId} 已提交");
Task.Delay(TimeSpan.FromSeconds(0.5)).Wait(); // 模拟订单提交间隔
}
// 等待所有订单处理完成
orderProcessingSystem.CompleteProcessing();
Console.WriteLine("所有订单处理完成!");
Console.ReadKey();
}
}
示例代码说明:
-
Order
类 :定义了订单的基本属性,包括订单编号、菜品名称、订单时间和估计烹饪时间。 -
OrderProcessingSystem
类 :负责初始化和管理ActionBlock<Order>
。ActionBlock<Order>
的构造函数中传入了一个异步 lambda 表达式,用于定义每个订单的处理逻辑。ExecutionDataflowBlockOptions
用于设置ActionBlock<Order>
的选项,包括最大并行度和是否保证任务执行顺序。
-
ProcessOrderAsync
方法 :模拟了订单的处理过程,包括打印订单开始处理的信息、模拟处理延迟和打印订单完成处理的信息。 -
AddOrder
方法 :用于将订单添加到ActionBlock<Order>
的输入队列中。 -
CompleteProcessing
方法 :用于标记ActionBlock<Order>
为完成状态,并等待所有订单处理完成。 -
Main
方法 :模拟接收多个订单,将它们添加到OrderProcessingSystem
中,并等待所有订单处理完成。
这个示例代码演示了如何使用 ActionBlock<Order>
来处理订单,展示了如何设置 ActionBlock<Order>
的选项、添加任务和等待任务完成的方法。
ActionBlock<Order>
的 EnsureOrdered
属性用途
在之前的示例中,ActionBlock<Order>
的 EnsureOrdered
属性被设置为 false
,这允许任务以任意顺序执行,以提高性能。如果需要确保订单处理的顺序性,可以将 EnsureOrdered
属性设置为 true
,这样任务将按照添加的顺序执行。
以下是修改后的示例代码,确保订单处理的顺序性:
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
public class Order
{
public int OrderId { get; set; }
public string DishName { get; set; }
public DateTime OrderTime { get; set; }
public int EstimatedCookingTimeInSeconds { get; set; }
}
public class OrderProcessingSystem
{
private ActionBlock<Order> _orderProcessingBlock;
public OrderProcessingSystem()
{
// 设置 ActionBlock<Order> 的执行策略
var actionBlockOptions = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1, // 设置最大并行度为1,确保顺序执行
EnsureOrdered = true // 确保任务执行顺序
};
// 创建 ActionBlock<Order>
_orderProcessingBlock = new ActionBlock<Order>(async order =>
{
await ProcessOrderAsync(order);
}, actionBlockOptions);
}
private async Task ProcessOrderAsync(Order order)
{
Console.WriteLine($"开始处理订单 {order.OrderId} ({order.DishName}),预计耗时 {order.EstimatedCookingTimeInSeconds} 秒");
await Task.Delay(TimeSpan.FromSeconds(order.EstimatedCookingTimeInSeconds)); // 模拟处理时间
Console.WriteLine($"订单 {order.OrderId} 处理完成");
}
public void AddOrder(Order order)
{
// 将订单添加到 ActionBlock<Order> 的输入队列中
_orderProcessingBlock.Post(order);
}
public void CompleteProcessing()
{
// 标记 ActionBlock<Order> 为完成状态,不再接受新的订单
_orderProcessingBlock.Complete();
// 等待所有订单处理完成
_orderProcessingBlock.Completion.Wait();
}
}
class Program
{
static void Main(string[] args)
{
var orderProcessingSystem = new OrderProcessingSystem();
// 模拟接收订单
for (int i = 1; i <= 10; i++)
{
var order = new Order
{
OrderId = i,
DishName = $"Dish_{i}",
OrderTime = DateTime.Now,
EstimatedCookingTimeInSeconds = new Random().Next(1, 5)
};
orderProcessingSystem.AddOrder(order);
Console.WriteLine($"订单 {order.OrderId} 已提交");
Task.Delay(TimeSpan.FromSeconds(0.5)).Wait(); // 模拟订单提交间隔
}
// 等待所有订单处理完成
orderProcessingSystem.CompleteProcessing();
Console.WriteLine("所有订单处理完成!");
Console.ReadKey();
}
}
修改点解释:
MaxDegreeOfParallelism
设置为 1 :这确保了ActionBlock<Order>
一次只处理一个任务,从而保证了任务的顺序性。EnsureOrdered
设置为true
:虽然当MaxDegreeOfParallelism
为 1 时,EnsureOrdered
的影响不大,但显式地将其设置为true
可以明确表示我们希望任务按照添加顺序执行的意图。
通过这些修改,订单将按照提交的顺序被处理。每个订单只有在前一个订单处理完成后才会开始处理。这种设置适用于需要严格保证订单处理顺序的场景。