修改框架(未完全完成)实现单个相机分开绑定算法
This commit is contained in:
		| @@ -25,39 +25,25 @@ namespace Check.Main.Camera | ||||
|     public static class CameraManager | ||||
|     { | ||||
|         //1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。 | ||||
|         // 活动的相机实例字典(键值对),键 (string):使用相机的名称,值 (HikvisionCamera):存储实际的相机实例,{ get; } 创建了一个只读的公共属性 | ||||
|  | ||||
|         public static Dictionary<string, HikvisionCamera> ActiveCameras { get; } = new Dictionary<string, HikvisionCamera>(); | ||||
|  | ||||
|         // 相机对应的图像显示窗口字典 | ||||
|         //public static Dictionary<string, FormImageDisplay> CameraDisplays { get; } = new Dictionary<string, FormImageDisplay>(); | ||||
|  | ||||
|         public static Dictionary<string, FormImageDisplay> OriginalImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//原始图像窗口 | ||||
|         public static Dictionary<string, FormImageDisplay> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//结果图像窗口 | ||||
|  | ||||
|         //2、多相机同步逻辑 | ||||
|         // 【队列】一个产品需要多台相机拍完,才算完整。 | ||||
|         private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>(); | ||||
|         // 【(队列)锁】保证队列在多线程下安全 | ||||
|         private static readonly object QueueLock = new object(); | ||||
|         // 当前启用的相机数量,用于判断产品是否检测完毕 | ||||
|         private static int EnabledCameraCount = 0; | ||||
|         //public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted; | ||||
|         // public static bool IsDetectionRunning { get; private set; } = false; | ||||
|         // 产品ID【计数器】 | ||||
|         private static long _productCounter = 0; | ||||
|         // 用于同步计数器访问的锁,虽然long的自增是原子操作,但为清晰和未来扩展,使用锁是好习惯 | ||||
|         private static readonly object _counterLock = new object(); | ||||
|  | ||||
|         // 3、--- 新增:硬触发模拟器 --- | ||||
|         private static readonly System.Timers.Timer _hardwareTriggerSimulator; | ||||
|         /// <summary> | ||||
|         /// 获取或设置模拟硬触发的间隔时间(毫秒)。 | ||||
|         /// </summary> | ||||
|               | ||||
|         // 获取或设置模拟硬触发的间隔时间(毫秒)。 | ||||
|         public static double TriggerInterval { get; set; } = 1000; // 默认1秒触发一次 | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 获取一个值,该值指示硬件触发模拟器当前是否正在运行。 | ||||
|         /// </summary> | ||||
|         // 获取一个值,该值指示硬件触发模拟器当前是否正在运行。 | ||||
|         public static bool IsHardwareTriggerSimulating { get; private set; } = false; | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -73,113 +59,36 @@ namespace Check.Main.Camera | ||||
|         } | ||||
|  | ||||
|  | ||||
|         //// 事件:用于向UI发送日志消息 | ||||
|         //public static event Action<string> OnLogMessage; | ||||
|         //// 用于写入日志文件的 StreamWriter | ||||
|         //private static StreamWriter _logFileWriter; | ||||
|  | ||||
|         ////私有的、静态的、只读的对象,专门用作线程同步的“锁”。 | ||||
|         //private static readonly object _logLock = new object(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 初始化文件日志记录器。应在程序启动时调用一次。 | ||||
|         /// </summary> | ||||
|         //public static void InitializeLogger() | ||||
|         //{ | ||||
|         //    try | ||||
|         //    { | ||||
|         //        string logDirectory = Path.Combine(Application.StartupPath, "Logs"); | ||||
|         //        Directory.CreateDirectory(logDirectory); // 确保Logs文件夹存在 | ||||
|  | ||||
|         //        string logFileName = $"Log_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt"; | ||||
|         //        string logFilePath = Path.Combine(logDirectory, logFileName); | ||||
|  | ||||
|         //        // 创建StreamWriter,设置为追加模式和自动刷新 | ||||
|         //        _logFileWriter = new StreamWriter(logFilePath, append: true, encoding: System.Text.Encoding.UTF8) | ||||
|         //        { | ||||
|         //            AutoFlush = true | ||||
|         //        }; | ||||
|  | ||||
|         //        Log("文件日志记录器已初始化。"); | ||||
|         //    } | ||||
|         //    catch (Exception ex) | ||||
|         //    { | ||||
|         //        // 如果文件日志初始化失败,在UI上报告错误 | ||||
|         //        OnLogMessage?.Invoke($"[CRITICAL] 文件日志初始化失败: {ex.Message}"); | ||||
|         //    } | ||||
|         //} | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 【新增】一个完整的业务流程方法: | ||||
|         /// 1. 根据配置初始化所有相机并显示它们的窗口。 | ||||
|         /// 2. 在所有窗口都显示后,命令所有相机开始采集。 | ||||
|         /// 这个方法是响应“开启设备”或“应用配置并启动”按钮的理想入口点。 | ||||
|         /// </summary> | ||||
|         /// <param name="settingsList">要应用的相机配置列表。</param> | ||||
|         /// <param name="mainForm">主窗体,用于停靠显示窗口。</param> | ||||
|         //public static void ApplyConfigurationAndStartGrabbing(List<CameraSettings> settingsList, FrmMain mainForm) | ||||
|         //{ | ||||
|         //    ThreadSafeLogger.Log("开始执行“开启设备”流程..."); | ||||
|  | ||||
|         //    // 步骤 1: 初始化所有相机和UI窗口 | ||||
|         //    // Initialize 方法会负责 Shutdown 旧实例、创建新实例、打开硬件、显示窗口等。 | ||||
|         //    // 因为 Initialize 方法中的 displayForm.Show() 是非阻塞的,它会立即返回, | ||||
|         //    // 窗体的创建和显示过程会被调度到UI线程的消息队列中。 | ||||
|         //    Initialize(settingsList, mainForm); | ||||
|  | ||||
|         //    // 检查是否有任何相机成功初始化 | ||||
|         //    if (ActiveCameras.Count == 0) | ||||
|         //    { | ||||
|         //        ThreadSafeLogger.Log("“开启设备”流程中止,因为没有相机被成功初始化。"); | ||||
|         //        return; | ||||
|         //    } | ||||
|  | ||||
|         //    // 步骤 2: 在 Initialize 完成后(意味着所有窗口都已创建并 Show),开始采集 | ||||
|         //    // 这个调用会紧接着 Initialize 执行,此时窗体可能还在绘制过程中,但这没关系。 | ||||
|         //    // StartAll() 会启动后台线程开始拉取图像,一旦图像到达,就会通过事件推送到已经存在的窗体上。 | ||||
|         //    ThreadSafeLogger.Log("所有相机窗口已创建,现在开始采集图像..."); | ||||
|         //    StartAll(); | ||||
|  | ||||
|         //    ThreadSafeLogger.Log("“开启设备”流程已完成。相机正在采集中。"); | ||||
|         //} | ||||
|          | ||||
|  | ||||
|         ////********************初始化和启动流程******************* | ||||
|         ///// <summary> | ||||
|         ///// 根据配置列表初始化或更新所有相机 | ||||
|         ///// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。 | ||||
|         ///// 这是“启动设备”的第一阶段。 | ||||
|         ///// </summary> | ||||
|         //public static void Initialize(List<CameraSettings> settingsList, FrmMain mainForm) | ||||
|         //public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型 | ||||
|         //{ | ||||
|  | ||||
|         //    // 先停止并释放所有旧的相机 | ||||
|         //    // 1. 清理旧资源和UI | ||||
|         //    mainForm.ClearStatusStrip(); | ||||
|         //    Shutdown(); | ||||
|         //    ThreadSafeLogger.Log("开始应用新的相机配置..."); | ||||
|         //    ThreadSafeLogger.Log("开始准备设备和模型..."); | ||||
|  | ||||
|         //    EnabledCameraCount = settingsList.Count(s => s.IsEnabled); | ||||
|         //    if (EnabledCameraCount == 0) | ||||
|         //    { | ||||
|         //        ThreadSafeLogger.Log("没有启用的相机。"); | ||||
|         //        return; | ||||
|         //    } | ||||
|         //    // 2. 初始化检测协调器和AI模型 | ||||
|         //    // 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中 | ||||
|         //    YoloModelManager.Initialize(config.ModelSettings); | ||||
|         //    DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings); | ||||
|  | ||||
|         //    var deviceList = new HikvisionCamera().FindDevices();  | ||||
|         //    // 3. 创建相机硬件实例和UI窗口------ | ||||
|         //    var deviceList = new HikvisionCamera().FindDevices(); | ||||
|         //    if (deviceList.Count == 0) | ||||
|         //    { | ||||
|         //        ThreadSafeLogger.Log("错误:未找到任何相机设备!"); | ||||
|         //        return; | ||||
|         //    } | ||||
|  | ||||
|         //    int deviceIndex = 0; | ||||
|         //    foreach (var setting in settingsList) | ||||
|         //    foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。 | ||||
|         //    { | ||||
|         //        if (!setting.IsEnabled) continue; | ||||
|  | ||||
|         //        if (deviceIndex >= deviceList.Count) | ||||
|         //        { | ||||
|         //            ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。"); | ||||
|         //            continue; | ||||
|         //        } | ||||
|  | ||||
|         //        // --- 创建相机实例 --- | ||||
|         //        var cam = new HikvisionCamera { Name = setting.Name }; | ||||
|         //        var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex }; | ||||
|         //        cam.TriggerMode = setting.TriggerMode; | ||||
|         //        if (!cam.Open(setting)) | ||||
|         //        { | ||||
| @@ -203,40 +112,38 @@ namespace Check.Main.Camera | ||||
|  | ||||
|         //        // --- 订阅事件 --- | ||||
|         //        cam.ImageAcquired += OnCameraImageAcquired; | ||||
|         //        cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}"); | ||||
|  | ||||
|         //        // --- 创建显示窗口 --- | ||||
|         //        var displayForm = new FormImageDisplay { Text = setting.Name, CameraName = setting.Name }; | ||||
|         //        displayForm.OnDisplayEvent += ThreadSafeLogger.Log; | ||||
|         //        displayForm.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||
|         //        var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex); | ||||
|         //        if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; } | ||||
|  | ||||
|         //        // --- 创建【但不显示】图像的UI窗口 --- | ||||
|         //        var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name }; | ||||
|         //        var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name }; | ||||
|         //        originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||
|         //        resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||
|  | ||||
|         //        // --- 保存引用 --- | ||||
|         //        ActiveCameras.Add(setting.Name, cam); | ||||
|         //        CameraDisplays.Add(setting.Name, displayForm); | ||||
|         //        OriginalImageDisplays.Add(setting.Name, originalDisplay); | ||||
|         //        ResultImageDisplays.Add(setting.Name, resultDisplay); | ||||
|         //        mainForm.AddCameraToStatusStrip(setting.Name); | ||||
|         //        ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。"); | ||||
|         //        deviceIndex++; | ||||
|         //    } | ||||
|         //    ThreadSafeLogger.Log("所有设备和模型已准备就绪。"); | ||||
|         //} | ||||
|  | ||||
|         //********************初始化和启动流程******************* | ||||
|         /// <summary> | ||||
|         /// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。 | ||||
|         /// 这是“启动设备”的第一阶段。 | ||||
|         /// </summary> | ||||
|         public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型 | ||||
|         public static void PrepareAll(ProcessConfig config, FrmMain mainForm) | ||||
|         { | ||||
|             // 1. 清理旧资源和UI | ||||
|             mainForm.ClearStatusStrip(); | ||||
|             Shutdown(); | ||||
|             mainForm.ClearStatusStrip(); // 清理旧的状态条 | ||||
|             Shutdown(); // 清理旧的相机和协调器资源 | ||||
|             ThreadSafeLogger.Log("开始准备设备和模型..."); | ||||
|  | ||||
|             // 2. 初始化检测协调器和AI模型 | ||||
|             // 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中 | ||||
|             YoloModelManager.Initialize(config.ModelSettings); | ||||
|             // 1. 初始化检测协调器和AI模型(此步骤会加载YOLO模型并创建CameraProcessor) | ||||
|             DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings); | ||||
|  | ||||
|             // 3. 创建相机硬件实例和UI窗口------ | ||||
|             // 2. 创建相机硬件实例和UI窗口 | ||||
|             var deviceList = new HikvisionCamera().FindDevices(); | ||||
|             if (deviceList.Count == 0) | ||||
|             { | ||||
| @@ -244,8 +151,15 @@ namespace Check.Main.Camera | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。 | ||||
|             int deviceIndex = 0; | ||||
|             foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled)) | ||||
|             { | ||||
|                 if (deviceIndex >= deviceList.Count) | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。"); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex }; | ||||
|                 cam.TriggerMode = setting.TriggerMode; | ||||
|                 if (!cam.Open(setting)) | ||||
| @@ -270,12 +184,26 @@ namespace Check.Main.Camera | ||||
|  | ||||
|                 // --- 订阅事件 --- | ||||
|                 cam.ImageAcquired += OnCameraImageAcquired; | ||||
|                 var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex); | ||||
|                 if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; } | ||||
|                 cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}"); | ||||
|  | ||||
|                 // --- 创建【但不显示】图像的UI窗口 --- | ||||
|                 // 获取 CameraProcessor 并订阅其完成事件 | ||||
|                 var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex); | ||||
|                 if (processor != null) | ||||
|                 { | ||||
|                     processor.OnProcessingCompleted += Processor_OnProcessingCompleted; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log($"[警告] 未能为相机 '{setting.Name}' (Index: {setting.CameraIndex}) 获取处理器,检测结果将不会显示。"); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 // --- 创建【并显示】图像的UI窗口 --- | ||||
|                 var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name }; | ||||
|                 var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name }; | ||||
|                 originalDisplay.OnDisplayEvent += ThreadSafeLogger.Log; | ||||
|                 resultDisplay.OnDisplayEvent += ThreadSafeLogger.Log; | ||||
|  | ||||
|                 originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||
|                 resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||
|  | ||||
| @@ -284,6 +212,8 @@ namespace Check.Main.Camera | ||||
|                 OriginalImageDisplays.Add(setting.Name, originalDisplay); | ||||
|                 ResultImageDisplays.Add(setting.Name, resultDisplay); | ||||
|                 mainForm.AddCameraToStatusStrip(setting.Name); | ||||
|                 ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。"); | ||||
|                 deviceIndex++; | ||||
|             } | ||||
|             ThreadSafeLogger.Log("所有设备和模型已准备就绪。"); | ||||
|         } | ||||
| @@ -507,8 +437,6 @@ namespace Check.Main.Camera | ||||
|             // 3. 关闭检测协调器,它会负责清理所有后台线程和队列 | ||||
|             DetectionCoordinator.Shutdown(); | ||||
|  | ||||
|             YoloModelManager.Shutdown(); | ||||
|  | ||||
|             ThreadSafeLogger.Log("所有相机及协调器资源已释放。"); | ||||
|         } | ||||
|  | ||||
| @@ -517,10 +445,11 @@ namespace Check.Main.Camera | ||||
|         /// </summary> | ||||
|         public static void ResetProductCounter() | ||||
|         { | ||||
|             lock (_counterLock) | ||||
|             { | ||||
|                 _productCounter = 0; | ||||
|             } | ||||
|             //lock (_counterLock) | ||||
|             //{ | ||||
|             //    _productCounter = 0; | ||||
|             //} | ||||
|             DetectionCoordinator.ResetAllCounters(); // 调用协调器的重置方法             | ||||
|             ThreadSafeLogger.Log("产品计数器已重置。"); | ||||
|         } | ||||
|  | ||||
| @@ -620,6 +549,7 @@ namespace Check.Main.Camera | ||||
|         //} | ||||
|  | ||||
|         //【相机回调】 | ||||
|         // 【相机回调】现在只负责图像分发 | ||||
|         private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp) | ||||
|         { | ||||
|             Bitmap bmpForDisplay = null; | ||||
| @@ -627,66 +557,51 @@ namespace Check.Main.Camera | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 // 1. 深度克隆 Bitmap → 分成 显示副本 和 处理副本。 | ||||
|                 bmpForDisplay = DeepCloneBitmap(bmp, "Display"); | ||||
|                 bmpForProcessing = DeepCloneBitmap(bmp, "Processing"); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 // 2.无论克隆成功与否,都必须立即释放事件传递过来的原始bmp,防止泄漏。 | ||||
|                 bmp?.Dispose();//空条件运算符 ?.-----在访问对象成员前检查对象是否为 null,如果 bmp 不是 null,则正常调用 Dispose() 方法,替代传统的 if (bmp != null) 检查 | ||||
|                 bmp?.Dispose(); | ||||
|             } | ||||
|             // 分支 A: 显示副本 → 交给 FormImageDisplay.UpdateImage()。 | ||||
|  | ||||
|             if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow)) | ||||
|             { | ||||
|                 // displayWindow.UpdateImage 会处理线程安全问题 | ||||
|                 displayWindow.UpdateImage(bmpForDisplay); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // 如果没有对应的显示窗口,或克隆失败,必须释放为显示创建的副本 | ||||
|                 bmpForDisplay?.Dispose(); | ||||
|             } | ||||
|             // 分支 B: 处理副本 → 交给 DetectionCoordinator.EnqueueImage() 进行检测。 | ||||
|  | ||||
|             // 直接将处理副本和相机编号交给 DetectionCoordinator | ||||
|             if (bmpForProcessing != null) | ||||
|             { | ||||
|                 // bmpForProcessing 的所有权在这里被转移给了协调器 | ||||
|                 DetectionCoordinator.EnqueueImage(sender.CameraIndex, bmpForProcessing); | ||||
|             } | ||||
|             //// 深度克隆图像以确保线程安全 | ||||
|             //Bitmap bmpForProcessing = DeepCloneBitmap(bmp, "Processing"); | ||||
|             //bmp?.Dispose(); // 立即释放原始图 | ||||
|  | ||||
|             //// 直接将图像和相机编号交给协调器,无需任何本地处理 | ||||
|             //DetectionCoordinator.EnqueueImage(sender.CameraIndex, bmpForProcessing); | ||||
|         } | ||||
|         // 事件处理器 | ||||
|  | ||||
|         // 事件处理器:从 CameraProcessor 接收带有结果的图像 | ||||
|         private static void Processor_OnProcessingCompleted(object sender, ProcessingCompletedEventArgs e) | ||||
|         { | ||||
|  | ||||
|             // 1. 找到与此相机匹配的相机名称 | ||||
|             var cameraEntry = ActiveCameras.FirstOrDefault(kvp => kvp.Value.CameraIndex == e.CameraIndex); | ||||
|             if (cameraEntry.Key == null) | ||||
|             { | ||||
|                 e.Dispose(); // 如果找不到接收者,必须释放事件参数中的图像 | ||||
|                 e.Dispose(); | ||||
|                 return; | ||||
|             } | ||||
|             // 2. 找到此相机的结果显示窗口 | ||||
|  | ||||
|             if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay)) | ||||
|             { | ||||
|                 //var bmp = ConvertSKImageToBitmap(e.ResultImage); | ||||
|                 if (e.ResultImage != null) | ||||
|                 { | ||||
|                     // UpdateImage 会负责克隆并显示,所以这里传递 bmp 即可 | ||||
|                     resultDisplay.UpdateImage(e.ResultImage); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // 如果找到了相机但没有对应的结果窗口,也要释放图像 | ||||
|                 e.Dispose(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -808,32 +723,6 @@ namespace Check.Main.Camera | ||||
|  | ||||
|             return clone; | ||||
|         } | ||||
|  | ||||
|         // 触发日志事件的辅助方法 | ||||
|         //public static void Log(string message) | ||||
|         //{ | ||||
|         //    string formattedMessage = $"[{DateTime.Now:HH:mm:ss.fff}] {message}"; | ||||
|  | ||||
|         //    // 1. 触发事件,更新UI | ||||
|         //    OnLogMessage?.Invoke(formattedMessage); | ||||
|  | ||||
|         //    // 2. 【关键修改】在写入文件前,先获取锁。 | ||||
|         //    // lock 语句块确保了花括号内的代码在同一时间只能被一个线程执行。 | ||||
|         //    // 如果另一个线程也想执行这段代码,它必须等待前一个线程执行完毕并释放锁。 | ||||
|         //    lock (_logLock) | ||||
|         //    { | ||||
|         //        try | ||||
|         //        { | ||||
|         //            _logFileWriter?.WriteLine(formattedMessage); | ||||
|         //        } | ||||
|         //        catch (Exception) | ||||
|         //        { | ||||
|         //            // 即使有锁,也保留try-catch以防万一(如磁盘满了等IO问题)。 | ||||
|         //            // 可以在这里决定是否要在UI上显示警告。 | ||||
|         //        } | ||||
|         //    } | ||||
|         //} | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user