From fc74a33327eb17df21f5c2c88c8bb87a0e20bd78 Mon Sep 17 00:00:00 2001 From: WenJY Date: Sat, 13 Jun 2026 22:44:48 +0800 Subject: [PATCH] =?UTF-8?q?change=20-=20=E5=8C=85=E6=9D=90=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E8=B0=83=E5=BA=A6=E9=80=BB=E8=BE=91=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sln.Wcs.Business/TaskCreateService.cs | 2 + Sln.Wcs.Repository/Repository.cs | 12 +- Sln.Wcs.Strategy/MaterialInStoreExecutor.cs | 202 ++++++++++++++++ Sln.Wcs.UI/App.axaml.cs | 3 + Sln.Wcs.UI/Sln.Wcs.UI.csproj | 1 + .../ViewModels/SystemMonitorViewModel.cs | 41 +++- Sln.Wcs.UI/Views/SystemMonitorView.axaml | 28 ++- 任务执行调度-当前实现记录.md | 228 ++++++++++++++++++ 任务驱动的任务调度方案.md | 170 +++++++++++++ 9 files changed, 674 insertions(+), 13 deletions(-) create mode 100644 Sln.Wcs.Strategy/MaterialInStoreExecutor.cs create mode 100644 任务执行调度-当前实现记录.md create mode 100644 任务驱动的任务调度方案.md diff --git a/Sln.Wcs.Business/TaskCreateService.cs b/Sln.Wcs.Business/TaskCreateService.cs index d09332d..56e4f15 100644 --- a/Sln.Wcs.Business/TaskCreateService.cs +++ b/Sln.Wcs.Business/TaskCreateService.cs @@ -142,6 +142,7 @@ public class TaskCreateService materialCode = materialCode, taskSteps = details.Count, taskDetails = details, + isFlag = 1 }; } @@ -221,6 +222,7 @@ public class TaskCreateService taskType = taskType, taskCategory = taskCategory, taskStatus = 1, + isFlag = 1 }; #endregion diff --git a/Sln.Wcs.Repository/Repository.cs b/Sln.Wcs.Repository/Repository.cs index 80f56d8..e40447b 100644 --- a/Sln.Wcs.Repository/Repository.cs +++ b/Sln.Wcs.Repository/Repository.cs @@ -31,16 +31,14 @@ namespace Sln.Wcs.Repository { public class Repository : SimpleClient where T : class, new() { - public ITenant itenant = null; //多租户事务、GetConnection、IsAnyConnection等功能 + public ITenant itenant = null; public Repository(ISqlSugarClient db) { - itenant = db.AsTenant(); //用来处理事务 - base.Context = db.AsTenant().GetConnectionScopeWithAttr(); //获取子Db - //base.Context = db.AsTenant().GetConnectionScope("core"); - - //如果不想通过注入多个仓储 - //用到ChangeRepository或者Db.GetMyRepository需要看标题4写法 + itenant = db.AsTenant(); + // 直接用 SqlSugarScope 作为 Context,它内部管理连接池、天然线程安全 + // SqlSugarScope 根据 [Tenant] 特性自动路由到正确的数据库 + Context = (SqlSugarClient)db; } } } diff --git a/Sln.Wcs.Strategy/MaterialInStoreExecutor.cs b/Sln.Wcs.Strategy/MaterialInStoreExecutor.cs new file mode 100644 index 0000000..698209c --- /dev/null +++ b/Sln.Wcs.Strategy/MaterialInStoreExecutor.cs @@ -0,0 +1,202 @@ +using System.Collections.Concurrent; +using Sln.Wcs.Model.Domain; +using Sln.Wcs.Repository.service; +using Sln.Wcs.Serilog; + +namespace Sln.Wcs.Strategy; + +/// +/// 包材入库任务执行器 (taskCategory=1, taskType=1) +/// 轮询待执行任务 → 多任务并行、单任务内串行逐条下发明细 +/// +public class MaterialInStoreExecutor +{ + private readonly SerilogHelper _logger; + private readonly ILiveTaskQueueService _taskQueueService; + private readonly ILiveTaskDetailService _taskDetailService; + + // 每栋楼提升机限 2 台并发,AGV 不限 + private static readonly ConcurrentDictionary HoistSemaphores = new(); + + // DB 写操作互斥锁(SqlSugar Context 共享导致写入不能并发) + private static readonly object DbWriteLock = new(); + + // 最大同时执行任务数 + private static readonly SemaphoreSlim TaskSemaphore = new(10, 10); + + private readonly object _lock = new(); + private CancellationTokenSource? _cts; + private volatile bool _isRunning; + + public bool IsRunning => _isRunning; + + public MaterialInStoreExecutor( + SerilogHelper logger, + ILiveTaskQueueService taskQueueService, + ILiveTaskDetailService taskDetailService) + { + _logger = logger; + _taskQueueService = taskQueueService; + _taskDetailService = taskDetailService; + } + + public void Start() + { + lock (_lock) + { + if (_isRunning) return; + _cts = new CancellationTokenSource(); + _isRunning = true; + } + Task.Run(() => RunLoopAsync(_cts!.Token)); + _logger.Info("包材入库调度已启动"); + } + + public void Stop() + { + CancellationTokenSource? cts; + lock (_lock) + { + if (!_isRunning) return; + cts = _cts; + _isRunning = false; + } + cts?.Cancel(); + _logger.Info("包材入库调度已停止"); + } + + private async Task RunLoopAsync(CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + try { await Task.Run(() => ExecuteAsync(ct), ct); } + catch (OperationCanceledException) { break; } + catch (Exception ex) { _logger.Error($"调度异常: {ex.Message}"); } + + try { await Task.Delay(5000, ct); } + catch (OperationCanceledException) { break; } + } + } + + private void ExecuteAsync(CancellationToken ct) + { + var tasks = _taskQueueService.getLiveTaskQueues(x => + x.taskType == 1 && x.taskCategory == 1 && x.taskStatus == 1); + + if (tasks.Count == 0) return; + + _logger.Info($"查询到 {tasks.Count} 条待执行包材入库任务"); + + var threads = new List(); + foreach (var task in tasks) + { + var captured = task; + var t = new Thread(() => + { + TaskSemaphore.Wait(); + try + { + if (ct.IsCancellationRequested) return; + _logger.Info($"[线程启动] {captured.taskCode}"); + ProcessOneAsync(captured, ct).GetAwaiter().GetResult(); + } + catch (Exception ex) { _logger.Error($"任务 {captured.taskCode} 线程异常: {ex.Message}"); } + finally { TaskSemaphore.Release(); } + }) + { IsBackground = true }; + t.Start(); + threads.Add(t); + } + + foreach (var t in threads) + t.Join(); + } + + private async Task ProcessOneAsync(LiveTaskQueue task, CancellationToken ct) + { + _logger.Info($"开始执行 {task.taskCode},共 {task.taskDetails.Count} 条明细"); + + lock (DbWriteLock) { task.taskStatus = 2; _taskQueueService.Update(task); } + + foreach (var detail in task.taskDetails.OrderBy(d => d.objId)) + { + if (ct.IsCancellationRequested) return; + + if (detail.taskStatus is 2 or 3) + { + _logger.Info($" 明细 {detail.objId} 已处理,跳过"); + continue; + } + + lock (DbWriteLock) { detail.taskStatus = 2; _taskDetailService.Update(detail); } + + var devName = detail.deviceType switch { 1 => "AGV", 2 => "提升机", _ => "输送线" }; + _logger.Info($" → {devName}下发 {detail.taskCode}-{detail.objId}: {detail.startPoint} → {detail.endPoint}"); + + // 模拟执行在锁外,多任务并行不受影响 + var ok = detail.deviceType switch + { + 1 => await DispatchAgvAsync(detail), + 2 => await DispatchHoistAsync(detail), + _ => await DispatchConveyorAsync(detail) + }; + + if (ok) + { + lock (DbWriteLock) { detail.taskStatus = 3; _taskDetailService.Update(detail); } + _logger.Info($" ✓ {detail.objId} 完成"); + } + else + { + lock (DbWriteLock) { detail.taskStatus = 1; _taskDetailService.Update(detail); } + _logger.Error($" ✗ {detail.objId} 失败,中断任务"); + return; + } + } + + lock (DbWriteLock) { task.taskStatus = 3; _taskQueueService.Update(task); } + _logger.Info($"任务 {task.taskCode} 执行完成"); + } + + private async Task DispatchAgvAsync(LiveTaskDetail detail) + { + // AGV 不限并发,直接下发 + _logger.Info($" [AGV] 开始 {detail.startPoint} → {detail.endPoint}"); + await Task.Delay(10_000); // 模拟执行 10s + _logger.Info($" [AGV] 完成 {detail.startPoint} → {detail.endPoint}"); + return true; + } + + private async Task DispatchHoistAsync(LiveTaskDetail detail) + { + // 每栋楼提升机限 2 台并发 + var building = ExtractBuilding(detail.startPoint); + var semaphore = HoistSemaphores.GetOrAdd(building, _ => new SemaphoreSlim(2, 2)); + + await semaphore.WaitAsync(); + try + { + _logger.Info($" [提升机] {building}#楼 开始 (可用:{semaphore.CurrentCount}) {detail.startPoint} → {detail.endPoint}"); + await Task.Delay(20_000); // 模拟执行 20s + _logger.Info($" [提升机] {building}#楼 完成 {detail.startPoint} → {detail.endPoint}"); + return true; + } + finally + { + semaphore.Release(); + } + } + + private Task DispatchConveyorAsync(LiveTaskDetail detail) + { + _logger.Info($" [输送线] {detail.startPoint} → {detail.endPoint}"); + return Task.FromResult(true); + } + + /// 从接驳位编码中提取楼栋号,如 "13#_L1_HOIST" → 13 + private static int ExtractBuilding(string pointCode) + { + var match = System.Text.RegularExpressions.Regex.Match(pointCode, @"^(\d+)"); + return match.Success ? int.Parse(match.Groups[1].Value) : 0; + } +} diff --git a/Sln.Wcs.UI/App.axaml.cs b/Sln.Wcs.UI/App.axaml.cs index 30d2fc9..08b5e1e 100644 --- a/Sln.Wcs.UI/App.axaml.cs +++ b/Sln.Wcs.UI/App.axaml.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using NeoSmart.Caching.Sqlite; using Sln.Wcs.Repository; using Sln.Wcs.Serilog; +using Sln.Wcs.Strategy; using Sln.Wcs.UI.Services; using Sln.Wcs.UI.ViewModels; using Sln.Wcs.UI.Views; @@ -59,6 +60,7 @@ public partial class App : Application Assembly.LoadFrom(Path.Combine(basePath, "Sln.Wcs.Cache.dll")), Assembly.LoadFrom(Path.Combine(basePath, "Sln.Wcs.Repository.dll")), Assembly.LoadFrom(Path.Combine(basePath, "Sln.Wcs.Business.dll")), + Assembly.LoadFrom(Path.Combine(basePath, "Sln.Wcs.Strategy.dll")), }; services.Scan(scan => scan.FromAssemblies(assemblies) @@ -99,6 +101,7 @@ public partial class App : Application // 引擎进程管理 services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); diff --git a/Sln.Wcs.UI/Sln.Wcs.UI.csproj b/Sln.Wcs.UI/Sln.Wcs.UI.csproj index 878a35f..dd664bc 100644 --- a/Sln.Wcs.UI/Sln.Wcs.UI.csproj +++ b/Sln.Wcs.UI/Sln.Wcs.UI.csproj @@ -37,6 +37,7 @@ + diff --git a/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs b/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs index e155a42..b719799 100644 --- a/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs +++ b/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Sln.Wcs.Strategy; using Sln.Wcs.UI.Services; namespace Sln.Wcs.UI.ViewModels; @@ -12,6 +13,7 @@ public partial class SystemMonitorViewModel : ObservableObject { private readonly IEngineProcessService _hoistEngine; private readonly IAgvEngineProcessService _agvEngine; + private readonly MaterialInStoreExecutor _dispatchExecutor; [ObservableProperty] private ObservableCollection _logs = new(); @@ -22,6 +24,11 @@ public partial class SystemMonitorViewModel : ObservableObject [ObservableProperty] private bool _autoScroll = true; + // ---- Dispatch ---- + [ObservableProperty] private bool _isDispatchRunning; + [ObservableProperty] private string _dispatchStatusText = "已停止"; + public string DispatchStatusColor => IsDispatchRunning ? "#00E676" : "#FF5252"; + // ---- Hoist ---- [ObservableProperty] private bool _isHoistRunning; [ObservableProperty] private string _hoistStatusText = "已停止"; @@ -34,16 +41,22 @@ public partial class SystemMonitorViewModel : ObservableObject private const int MaxLogs = 5000; - public SystemMonitorViewModel(IEngineProcessService hoistEngine, IAgvEngineProcessService agvEngine) + public SystemMonitorViewModel( + IEngineProcessService hoistEngine, + IAgvEngineProcessService agvEngine, + MaterialInStoreExecutor dispatchExecutor) { _hoistEngine = hoistEngine; _agvEngine = agvEngine; + _dispatchExecutor = dispatchExecutor; _hoistEngine.StateChanged += () => RefreshHoist(); _hoistEngine.OutputReceived += msg => AddLog($"[提升机] {msg}"); _agvEngine.StateChanged += () => RefreshAgv(); _agvEngine.OutputReceived += msg => AddLog($"[AGV] {msg}"); + StartDispatchCommand = new RelayCommand(StartDispatch, () => !IsDispatchRunning); + StopDispatchCommand = new RelayCommand(StopDispatch, () => IsDispatchRunning); StartHoistCommand = new AsyncRelayCommand(StartHoistAsync, () => !IsHoistRunning); StopHoistCommand = new RelayCommand(StopHoist, () => IsHoistRunning); StartAgvCommand = new AsyncRelayCommand(StartAgvAsync, () => !IsAgvRunning); @@ -108,6 +121,32 @@ public partial class SystemMonitorViewModel : ObservableObject } private void StopAgv() { _agvEngine.Stop(); AddLog("HikRoBotServer 已停止"); } + // ---- Dispatch Commands ---- + public IRelayCommand StartDispatchCommand { get; } + public IRelayCommand StopDispatchCommand { get; } + + private void StartDispatch() + { + _dispatchExecutor.Start(); + RefreshDispatch(); + } + private void StopDispatch() + { + _dispatchExecutor.Stop(); + RefreshDispatch(); + } + private void RefreshDispatch() + { + Avalonia.Threading.Dispatcher.UIThread.Post(() => + { + IsDispatchRunning = _dispatchExecutor.IsRunning; + DispatchStatusText = _dispatchExecutor.IsRunning ? "运行中" : "已停止"; + OnPropertyChanged(nameof(DispatchStatusColor)); + StartDispatchCommand.NotifyCanExecuteChanged(); + StopDispatchCommand.NotifyCanExecuteChanged(); + }); + } + private void AddLog(string msg, string level = "INFO") { Logs.Add(new LogEntry { Time = DateTime.Now, Message = msg, Level = level }); diff --git a/Sln.Wcs.UI/Views/SystemMonitorView.axaml b/Sln.Wcs.UI/Views/SystemMonitorView.axaml index 3623a89..67e9666 100644 --- a/Sln.Wcs.UI/Views/SystemMonitorView.axaml +++ b/Sln.Wcs.UI/Views/SystemMonitorView.axaml @@ -1,10 +1,28 @@ - - + + + + + + + + + + +