|
|
|
|
|
# 任务驱动的任务调度方案
|
|
|
|
|
|
|
|
|
|
|
|
## 一、核心思路
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
不是一次性下发全部步骤,也不是等待接驳位信号
|
|
|
|
|
|
|
|
|
|
|
|
任务创建后 (status=1)
|
|
|
|
|
|
→ 执行器轮询到 → 启动线程
|
|
|
|
|
|
→ 单任务内逐条串行下发明细
|
|
|
|
|
|
→ 前一条完成 → 自动执行下一条
|
|
|
|
|
|
→ 全部完成 → 任务结束
|
|
|
|
|
|
|
|
|
|
|
|
多任务之间并行执行,互不干扰
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 二、执行流程
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Start() 启动后台轮询
|
|
|
|
|
|
│
|
|
|
|
|
|
└─ RunLoopAsync (每 5 秒一轮)
|
|
|
|
|
|
│
|
|
|
|
|
|
└─ ExecuteAsync
|
|
|
|
|
|
│
|
|
|
|
|
|
├─ 查询 taskType=1, taskCategory=1, taskStatus=1
|
|
|
|
|
|
│
|
|
|
|
|
|
├─ foreach task → new Thread:
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ├─ TaskSemaphore.Wait() ← 最多 10 线程
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ └─ ProcessOneAsync(task):
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ├─ taskStatus → 2
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ├─ foreach detail (按 objId):
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
│ │ ├─ detailStatus → 2
|
|
|
|
|
|
│ │ ├─ 下发设备
|
|
|
|
|
|
│ │ │ 1 → AGV (不限并发)
|
|
|
|
|
|
│ │ │ 2 → 提升机 (每栋限 2)
|
|
|
|
|
|
│ │ │ 0 → 输送线
|
|
|
|
|
|
│ │ ├─ 等待执行完成
|
|
|
|
|
|
│ │ └─ detailStatus → 3
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ └─ taskStatus → 3
|
|
|
|
|
|
│
|
|
|
|
|
|
└─ Thread.Join() 等本轮全部完成 → 5 秒后下一轮
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 三、并发模型
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
任务 A: Detail 1 → Detail 2 → Detail 3 → ...
|
|
|
|
|
|
任务 B: Detail 1 → Detail 2 → Detail 3 → ... 三个任务同时跑
|
|
|
|
|
|
任务 C: Detail 1 → Detail 2 → Detail 3 → ...
|
|
|
|
|
|
|
|
|
|
|
|
单任务内: 串行 (前一条完成才执行下一条)
|
|
|
|
|
|
多任务间: 并行 (各自独立线程)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 限制
|
|
|
|
|
|
|
|
|
|
|
|
| 控制项 | 机制 | 值 |
|
|
|
|
|
|
|--------|------|-----|
|
|
|
|
|
|
| 最大并行任务数 | `SemaphoreSlim` | 10 |
|
|
|
|
|
|
| 提升机并行/栋 | `ConcurrentDictionary<int, SemaphoreSlim>` | 2 |
|
|
|
|
|
|
| AGV 并行 | 无限制 | — |
|
|
|
|
|
|
| DB 写入 | `lock (DbWriteLock)` | 串行 |
|
|
|
|
|
|
|
|
|
|
|
|
## 四、单任务处理详情
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
ProcessOneAsync(task):
|
|
|
|
|
|
|
|
|
|
|
|
Step 1 ─ 标记任务执行中
|
|
|
|
|
|
lock (DbWriteLock):
|
|
|
|
|
|
task.taskStatus = 2
|
|
|
|
|
|
Update(task)
|
|
|
|
|
|
|
|
|
|
|
|
Step 2 ─ 逐条处理明细 (按 objId 排序)
|
|
|
|
|
|
for each detail:
|
|
|
|
|
|
|
|
|
|
|
|
跳过已完成 (status 2 or 3)
|
|
|
|
|
|
|
|
|
|
|
|
lock (DbWriteLock):
|
|
|
|
|
|
detail.taskStatus = 2
|
|
|
|
|
|
Update(detail)
|
|
|
|
|
|
|
|
|
|
|
|
↓ 下发设备 (锁外,可并行)
|
|
|
|
|
|
deviceType:
|
|
|
|
|
|
1 → DispatchAgvAsync AGV 模拟 10s
|
|
|
|
|
|
2 → DispatchHoistAsync 提升机模拟 20s
|
|
|
|
|
|
0 → DispatchConveyorAsync 输送线
|
|
|
|
|
|
|
|
|
|
|
|
成功:
|
|
|
|
|
|
lock (DbWriteLock):
|
|
|
|
|
|
detail.taskStatus = 3
|
|
|
|
|
|
Update(detail)
|
|
|
|
|
|
|
|
|
|
|
|
失败:
|
|
|
|
|
|
lock (DbWriteLock):
|
|
|
|
|
|
detail.taskStatus = 1 回退待重试
|
|
|
|
|
|
Update(detail)
|
|
|
|
|
|
return (中断任务)
|
|
|
|
|
|
|
|
|
|
|
|
Step 3 ─ 标记任务完成
|
|
|
|
|
|
lock (DbWriteLock):
|
|
|
|
|
|
task.taskStatus = 3
|
|
|
|
|
|
Update(task)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 五、设备下发
|
|
|
|
|
|
|
|
|
|
|
|
| 设备类型 | deviceType | 方法 | 模拟延迟 | 并发控制 |
|
|
|
|
|
|
|---------|-----------|------|---------|---------|
|
|
|
|
|
|
| AGV | 1 | `DispatchAgvAsync` | 10s | 无限制 |
|
|
|
|
|
|
| 提升机 | 2 | `DispatchHoistAsync` | 20s | 每栋 2 台 |
|
|
|
|
|
|
| 输送线 | 0 | `DispatchConveyorAsync` | 即时 | — |
|
|
|
|
|
|
|
|
|
|
|
|
### 提升机并发控制
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// 从 startPoint 提取楼栋号: "14#_L2_HOIST" → 14
|
|
|
|
|
|
var building = ExtractBuilding(detail.startPoint);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取该楼栋信号量,首次自动创建 SemaphoreSlim(2, 2)
|
|
|
|
|
|
var semaphore = HoistSemaphores.GetOrAdd(building, _ => new SemaphoreSlim(2, 2));
|
|
|
|
|
|
|
|
|
|
|
|
await semaphore.WaitAsync(); // 等待有空闲提升机
|
|
|
|
|
|
// ... 执行 ...
|
|
|
|
|
|
semaphore.Release(); // 释放
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 六、执行示例
|
|
|
|
|
|
|
|
|
|
|
|
3 个包材入库任务,每个 6 条明细:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
T+0s [线程1] 任务A Detail1 AGV开始 [线程2] 任务B Detail1 AGV开始 [线程3] 任务C Detail1 AGV开始
|
|
|
|
|
|
T+10s [线程1] 任务A Detail1 完成 [线程2] 任务B Detail1 完成 [线程3] 任务C Detail1 完成
|
|
|
|
|
|
T+10s [线程1] 任务A Detail2 提升机开始 [线程2] 任务B Detail2 提升机开始 [线程3] 等待(13#提升机满)
|
|
|
|
|
|
T+30s [线程1] 任务A Detail2 完成 [线程2] 任务B Detail2 完成 [线程3] 任务C Detail2 提升机开始
|
|
|
|
|
|
T+30s [线程1] 任务A Detail3 AGV开始 [线程2] 任务B Detail3 AGV开始
|
|
|
|
|
|
...并行继续...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 七、状态流转
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
LiveTaskQueue.taskStatus:
|
|
|
|
|
|
1 (待执行) → 2 (执行中) → 3 (已完成)
|
|
|
|
|
|
|
|
|
|
|
|
LiveTaskDetail.taskStatus:
|
|
|
|
|
|
1 (待执行) → 2 (执行中) → 3 (已完成)
|
|
|
|
|
|
↘ 失败回退 → 1 (待重试)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 八、UI 控制
|
|
|
|
|
|
|
|
|
|
|
|
系统监控页面顶部卡片,点击启动/停止:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌──────────────────────────────────────────────────┐
|
|
|
|
|
|
│ ● 包材入库调度 运行中/已停止 [▶ 启动] [■ 停止] │
|
|
|
|
|
|
└──────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `Start()` → 创建 `CancellationTokenSource`,启动 `RunLoopAsync`
|
|
|
|
|
|
- `Stop()` → 取消 token,等待当前线程结束后标记停止
|