Răsfoiți Sursa

perf: 产品加载改为并发多线程,切换产品时释放旧产品 ToolBlock 内存

zly 1 lună în urmă
părinte
comite
e19016d613
1 a modificat fișierele cu 105 adăugiri și 35 ștergeri
  1. 105 35
      TeamAAS-VM/Services/ProductService.cs

+ 105 - 35
TeamAAS-VM/Services/ProductService.cs

@@ -7,6 +7,7 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows.Documents;
 using System.Windows.Interop;
@@ -524,37 +525,50 @@ namespace TeamAAS_VP.Services
             var p = GetProduct(id);
             if (p == null) return null;
 
+            // 释放上一个产品的 ToolBlock 内存
+            UnloadPreviousProduct(id);
+
             var root = GetProductsRootFullPath();
             string filePath = Path.Combine(root, p.Name, p.Name + ".cfg");
             var dir = Path.GetDirectoryName(filePath);
             if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
 
-            //foreach (var camera in p.CameraProcedures)
-            //{
-            //    foreach (var prc in camera.ProcedureModels)
-            //    {
-            //        string prcPath = Path.Combine(dir, camera.Camera, prc.Name + ".vpp");
-            //        prc.ToolBlock = CogSerializer.LoadObjectFromFile(prcPath) as CogToolBlock;
-            //    }
-            //}
-
+            // 收集所有需要加载的流程(ToolBlock 为空的)
+            var toLoad = new List<(string Path, ProcedureModel Model)>();
             foreach (var camera in p.CameraProcedures)
             {
                 foreach (var prc in camera.ProcedureModels)
                 {
                     if (prc.ToolBlock != null) continue;
-
                     string prcPath = Path.Combine(dir, camera.Camera, prc.Name + ".vpp");
+                    toLoad.Add((prcPath, prc));
+                }
+            }
 
-                    // 在独立 STA 线程中加载 VPP,避免 COM 封送到 UI 线程
-                    if (!_vppCache.TryGetValue(prcPath, out var cached))
+            if (toLoad.Count > 0)
+            {
+                var parallelOptions = new ParallelOptions
+                {
+                    MaxDegreeOfParallelism = Math.Min(toLoad.Count, Environment.ProcessorCount * 2)
+                };
+                Parallel.ForEach(toLoad, parallelOptions, item =>
+                {
+                    try
                     {
-                        cached = VisionProHelper.LoadVisionProObjectAsync<CogToolBlock>(prcPath).GetAwaiter().GetResult();
-                        _vppCache.TryAdd(prcPath, cached);
+                        if (!_vppCache.TryGetValue(item.Path, out var cached))
+                        {
+                            cached = VisionProHelper.LoadVisionProObjectAsync<CogToolBlock>(item.Path).GetAwaiter().GetResult();
+                            _vppCache.TryAdd(item.Path, cached);
+                        }
+                        item.Model.ToolBlock = cached;
                     }
-                    prc.ToolBlock = cached;
-                }
+                    catch (Exception ex)
+                    {
+                        LogHelper.logerror.Error($"加载产品视觉流程时出错,路径:{item.Path}", ex);
+                    }
+                });
             }
+
             return p;
         }
 
@@ -569,36 +583,53 @@ namespace TeamAAS_VP.Services
             var p = GetProduct(id);
             if (p == null) return null;
 
+            // 释放上一个产品的 ToolBlock 内存
+            UnloadPreviousProduct(id);
+
             var root = GetProductsRootFullPath();
             string filePath = Path.Combine(root, p.Name, p.Name + ".cfg");
             var dir = Path.GetDirectoryName(filePath);
             if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
 
-            string temp = string.Empty;
-            // 加载每个相机的流程ToolBlock
-            try
+            // 收集所有需要加载的流程(ToolBlock 为空的)
+            var toLoad = new List<(string Path, ProcedureModel Model)>();
+            foreach (var camera in p.CameraProcedures)
             {
-                foreach (var camera in p.CameraProcedures)
+                foreach (var prc in camera.ProcedureModels)
                 {
-                    foreach (var prc in camera.ProcedureModels)
-                    {
-                        if (prc.ToolBlock != null) continue;
+                    if (prc.ToolBlock != null) continue;
+                    string prcPath = Path.Combine(dir, camera.Camera, prc.Name + ".vpp");
+                    toLoad.Add((prcPath, prc));
+                }
+            }
 
-                        string prcPath = Path.Combine(dir, camera.Camera, prc.Name + ".vpp");
-                        temp = prcPath;
-                        // 异步加载 VPP,避免同步 I/O 阻塞调用线程(在独立 STA 线程中执行)
-                        if (!_vppCache.TryGetValue(prcPath, out var cached))
+            if (toLoad.Count > 0)
+            {
+                // 限制并发数量,避免同时创建过多 STA 线程
+                var semaphore = new SemaphoreSlim(Math.Min(toLoad.Count, Environment.ProcessorCount * 2));
+                var tasks = toLoad.Select(async item =>
+                {
+                    await semaphore.WaitAsync();
+                    try
+                    {
+                        if (!_vppCache.TryGetValue(item.Path, out var cached))
                         {
-                            cached = await VisionProHelper.LoadVisionProObjectAsync<CogToolBlock>(prcPath);
-                            _vppCache.TryAdd(prcPath, cached);
+                            cached = await VisionProHelper.LoadVisionProObjectAsync<CogToolBlock>(item.Path);
+                            _vppCache.TryAdd(item.Path, cached);
                         }
-                        prc.ToolBlock = cached;
+                        item.Model.ToolBlock = cached;
                     }
-                }
-            }
-            catch (Exception ex)
-            {
-                LogHelper.logerror.Error($"加载产品视觉流程时出错,路径:{temp}", ex);
+                    catch (Exception ex)
+                    {
+                        LogHelper.logerror.Error($"加载产品视觉流程时出错,路径:{item.Path}", ex);
+                    }
+                    finally
+                    {
+                        semaphore.Release();
+                    }
+                });
+
+                await Task.WhenAll(tasks);
             }
 
             return p;
@@ -675,6 +706,45 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 释放上一个产品(非目标产品)所有流程的 ToolBlock,释放 VisionPro 对象占用的内存。
+        /// </summary>
+        private void UnloadPreviousProduct(Guid targetProductId)
+        {
+            lock (_sync)
+            {
+                if (_currentProduct == Guid.Empty || _currentProduct == targetProductId)
+                    return;
+
+                if (_products.TryGetValue(_currentProduct, out var oldProduct))
+                {
+                    UnloadProductToolBlocks(oldProduct);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 释放指定产品所有流程的 ToolBlock 引用,释放 VisionPro 对象占用的内存。
+        /// </summary>
+        private void UnloadProductToolBlocks(ProductModel product)
+        {
+            if (product?.CameraProcedures == null) return;
+            foreach (var camera in product.CameraProcedures)
+            {
+                if (camera.ProcedureModels == null) continue;
+                foreach (var prc in camera.ProcedureModels)
+                {
+                    if (prc.ToolBlock != null)
+                    {
+                        prc.ToolBlock.Dispose();
+                        prc.ToolBlock = null;
+                    }
+                }
+            }
+            // 清空 VPP 缓存,因为缓存的 ToolBlock 已被 Dispose
+            _vppCache.Clear();
+        }
+
         /// <summary>
         /// 获取当前产品模型(若未设置则返回 null)。
         /// </summary>