TaskDispatchBackgroundService.cs
13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Rcs.Application.Shared;
using Rcs.Domain.Entities;
using Rcs.Domain.Enums;
using Rcs.Domain.Repositories;
using TaskStatus = Rcs.Domain.Entities.TaskStatus;
namespace Rcs.Infrastructure.Services
{
/// <summary>
/// 后台任务调度服务 - 循环调度等待中的任务分配给空闲机器人
/// @author zzy
/// </summary>
public class TaskDispatchBackgroundService : BackgroundService, ITaskDispatchService
{
private readonly ILogger<TaskDispatchBackgroundService> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly TimeSpan _dispatchInterval = TimeSpan.FromSeconds(5);
private const int MaxPendingTasksPerCycle = 10;
public TaskDispatchBackgroundService(
ILogger<TaskDispatchBackgroundService> logger,
IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
/// <summary>
/// 后台服务执行入口
/// @author zzy
/// </summary>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("[任务调度] 后台任务调度服务已启动");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await DispatchAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "[任务调度] 调度过程发生异常");
}
await Task.Delay(_dispatchInterval, stoppingToken);
}
_logger.LogInformation("[任务调度] 后台任务调度服务已停止");
}
/// <summary>
/// 执行一次任务调度
/// @author zzy
/// </summary>
public async Task<TaskDispatchResult> DispatchAsync(CancellationToken cancellationToken = default)
{
using var scope = _serviceProvider.CreateScope();
var taskRepo = scope.ServiceProvider.GetRequiredService<IRobotTaskRepository>();
var robotRepo = scope.ServiceProvider.GetRequiredService<IRobotRepository>();
var templateRepo = scope.ServiceProvider.GetRequiredService<ITaskTemplateRepository>();
// 1. 获取等待中的任务,按优先级升序(数值小优先级高)、创建时间升序排序,取前10个
var pendingTasks = (await taskRepo.GetByStatusAsync(TaskStatus.Pending, cancellationToken))
.OrderBy(t => t.Priority)
.ThenBy(t => t.CreatedAt)
.Take(MaxPendingTasksPerCycle)
.ToList();
if (!pendingTasks.Any())
{
return new TaskDispatchResult { Success = true, AssignedCount = 0, Message = "无待调度任务" };
}
int assignedCount = 0;
foreach (var task in pendingTasks)
{
var taskWithDetails = await taskRepo.GetByIdWithDetailsAsync(task.TaskId, cancellationToken);
if (taskWithDetails == null)
continue;
// 获取任务起点所在地图的空闲机器人,筛选起点和终点库位类型都支持的机器人
var idleRobot = await FindIdleRobotForTaskAsync(taskWithDetails, robotRepo,taskRepo, cancellationToken);
if (idleRobot == null) continue;
// 获取机器人对应的任务模板(包含步骤)
var template = await GetTemplateForRobotAsync(idleRobot, templateRepo, cancellationToken);
// 分配任务
task.RobotId = idleRobot.RobotId;
task.TaskTemplateId = template?.TemplateId;
task.Status = TaskStatus.Assigned;
task.UpdatedAt = DateTime.Now;
await taskRepo.UpdateAsync(task, cancellationToken);
await taskRepo.SaveChangesAsync(cancellationToken);
// 根据模板创建子任务
if (template != null && template.TaskSteps.Any())
{
await CreateSubTasksFromTemplateAsync(taskWithDetails, idleRobot, template, cancellationToken);
}
_logger.LogInformation("[任务调度] 任务 {TaskCode} 已分配给机器人 {RobotCode},模板: {TemplateCode}",
task.TaskCode, idleRobot.RobotCode, template?.TemplateCode ?? "无");
assignedCount++;
}
return new TaskDispatchResult
{
Success = true,
AssignedCount = assignedCount,
Message = $"本次调度完成,已分配 {assignedCount} 个任务"
};
}
/// <summary>
/// 根据任务起点所在地图和库位类型查找空闲机器人
/// 筛选起点和终点库位类型都支持的机器人
/// @author zzy
/// </summary>
private async Task<Robot?> FindIdleRobotForTaskAsync(
RobotTask taskWithDetails,
IRobotRepository robotRepo,
IRobotTaskRepository taskRepo,
CancellationToken cancellationToken)
{
if (taskWithDetails?.BeginLocation?.MapNode == null)
{
_logger.LogWarning("[任务调度] 任务 {TaskCode} 无起点库位信息", taskWithDetails.TaskCode);
return null;
}
if (taskWithDetails?.EndLocation?.MapNode == null)
{
_logger.LogWarning("[任务调度] 任务 {TaskCode} 无终点库位信息", taskWithDetails.TaskCode);
return null;
}
var mapId = taskWithDetails.BeginLocation?.MapNode.MapId;
// 获取起点和终点的库位类型
var beginLocationTypeId = taskWithDetails.BeginLocation?.MapNode.StorageLocationTypeId;
var endLocationTypeId = taskWithDetails.EndLocation?.MapNode.StorageLocationTypeId;
if (!beginLocationTypeId.HasValue || !endLocationTypeId.HasValue)
{
_logger.LogWarning("[任务调度] 任务 {TaskCode} 库位类型信息不完整", taskWithDetails.TaskCode);
return null;
}
// 获取库位类型详情
var locationTypeRepo = _serviceProvider.CreateScope().ServiceProvider
.GetRequiredService<IStorageLocationTypeRepository>();
var beginLocationType = await locationTypeRepo.GetByIdAsync(beginLocationTypeId.Value, cancellationToken);
var endLocationType = await locationTypeRepo.GetByIdAsync(endLocationTypeId.Value, cancellationToken);
if (beginLocationType == null || endLocationType == null)
{
_logger.LogWarning("[任务调度] 任务 {TaskCode} 库位类型不存在", taskWithDetails.TaskCode);
return null;
}
// 获取该地图上所有空闲且在线的机器人
var idleRobots = await robotRepo.GetIdleRobotsAsync(cancellationToken);
// 先进行同步条件筛选
var candidateRobots = idleRobots
.Where(r => r.CurrentMapCodeId == mapId
&& r.Online == OnlineStatus.Online
&& r.Active == true
&& r.Driving == false
&& r.Paused == false
&& r.CurrentNodeId != null
&& r.RobotModel != null
&& beginLocationType.RobotModels.Contains(r.RobotModel)
&& endLocationType.RobotModels.Contains(r.RobotModel))
.ToList();
// 异步筛选:排除有进行中任务的机器人
Robot? availableRobot = null;
foreach (var robot in candidateRobots)
{
var robotTasks = await taskRepo.GetByRobotIdAsync(robot.RobotId);
if (!robotTasks.Any(t => t.IsInProgress()))
{
availableRobot = robot;
break;
}
}
return availableRobot;
}
/// <summary>
/// 获取机器人对应的任务模板(包含步骤和属性)
/// 优先获取默认模板,默认模板优先
/// @author zzy
/// </summary>
private async Task<TaskTemplate?> GetTemplateForRobotAsync(
Robot robot,
ITaskTemplateRepository templateRepo,
CancellationToken cancellationToken)
{
// 优先获取该机器人类型和制造商的默认模板
var template = await templateRepo.GetDefaultTemplateAsync(
robot.RobotType,
robot.RobotManufacturer,
cancellationToken: cancellationToken);
TaskTemplate? resultTemplate = null;
if (template != null)
{
// 获取包含完整详情的模板(步骤、属性、动作)
resultTemplate = await templateRepo.GetWithFullDetailsAsync(template.TemplateId, cancellationToken);
}
if (resultTemplate != null) return resultTemplate;
// 如果没有默认模板,获取该机器人类型的任意启用模板
var templates = await templateRepo.GetByRobotTypeAsync(robot.RobotType, cancellationToken);
var fallbackTemplate = templates.FirstOrDefault(t => t.IsEnabled);
if (fallbackTemplate != null)
{
// 获取包含完整详情的模板(步骤、属性、动作)
resultTemplate = await templateRepo.GetWithFullDetailsAsync(fallbackTemplate.TemplateId, cancellationToken);
}
return resultTemplate;
}
/// <summary>
/// 根据模板中的步骤创建子任务
/// 以模板中的order排序创建子任务
/// 除了第一个子任务,后续的子任务的起点都是上一个子任务的终点
/// 根据step中的Node属性类型(NodeValueType)来确定终点
/// @author zzy
/// </summary>
private async Task CreateSubTasksFromTemplateAsync(
RobotTask taskWithDetails,
Robot robot,
TaskTemplate template,
CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var subTaskRepo = scope.ServiceProvider.GetRequiredService<IRobotSubTaskRepository>();
var taskRepo = scope.ServiceProvider.GetRequiredService<IRobotTaskRepository>();
// 按order排序获取模板步骤
var orderedSteps = template.TaskSteps
.OrderBy(s => s.Order)
.ToList();
if (orderedSteps.Count == 0)
{
_logger.LogWarning("[任务调度] 模板 {TemplateCode} 无步骤配置", template.TemplateCode);
return;
}
if (taskWithDetails?.BeginLocation?.MapNode == null
|| taskWithDetails?.EndLocation?.MapNode == null)
{
_logger.LogWarning("[任务调度] 任务 {TaskCode} 缺少起点或终点节点信息", taskWithDetails.TaskCode);
return;
}
Guid beginNodeId = taskWithDetails.BeginLocation.MapNode.NodeId;
Guid endNodeId = taskWithDetails.EndLocation.MapNode.NodeId;
// 跟踪上一个子任务的终点
Guid previousEndNodeId = (Guid)robot.CurrentNodeId;
int sequence = 1;
foreach (var step in orderedSteps)
{
var subTask = new RobotSubTask
{
SubTaskId = Guid.NewGuid(),
TaskId = taskWithDetails.TaskId,
RobotId = robot.RobotId,
Status = TaskStatus.Pending,
CreatedAt = DateTime.Now,
Sequence = sequence
};
// 设置子任务起点:第一个为机器人当前节点,后续为上一个子任务的终点
subTask.BeginNodeId = previousEndNodeId;
// 根据步骤的Node属性确定终点
var nodeProperty = step.Properties.FirstOrDefault(p => p.PropertyType == StepPropertyType.Node);
subTask.EndNodeId = nodeProperty?.NodeValue.HasValue == true
? nodeProperty.NodeValue.Value switch
{
NodeValueType.Ts => beginNodeId, // 任务起点
NodeValueType.Te => endNodeId, // 任务终点
NodeValueType.Ws => endNodeId, // 工位集合 - 暂时使用任务终点,后续可根据具体业务逻辑确定工位节点
_ => endNodeId // 默认使用任务终点
}
: endNodeId; // 没有配置Node属性,默认使用任务终点
await subTaskRepo.AddAsync(subTask, cancellationToken);
previousEndNodeId = subTask.EndNodeId;
sequence++;
_logger.LogInformation("[任务调度] 创建子任务: 任务={TaskCode}, 子任务ID={SubTaskId}, 顺序={Sequence}, 起点={BeginNode}, 终点={EndNode}",
taskWithDetails.TaskCode, subTask.SubTaskId, subTask.Sequence, subTask.BeginNodeId, subTask.EndNodeId);
}
await subTaskRepo.SaveChangesAsync(cancellationToken);
}
}
}