6.7 弱引用

弱引用(Weak Reference):极简工业上位机视角解读

一、先搞懂「强引用」(对比才能懂弱引用)

强引用 = 用绳子拴住设备

比如你代码里写:

csharp 复制代码
var vm = new VisionViewModel(); // 强引用:vm变量像绳子拴住ViewModel实例

只要这根"绳子"还在(变量没置空、没出作用域),GC(垃圾回收器)就绝对不会回收这个ViewModel------哪怕界面已经关闭,实例依然占着内存,就像工人下班了但还被绳子拴在工位上,导致内存泄漏。

二、弱引用(WeakReferenceMessenger的核心)

弱引用 = 用粉笔在设备上做标记

WeakReferenceMessenger注册消息时,对接收者(比如你的VM)只做"粉笔标记",不拴绳子:

  1. 当VM该被回收时(比如窗口关闭、Dispose调用),GC能直接清理VM,不受Messenger影响
  2. 标记会自动消失,不会像强引用那样"拴住"实例导致内存泄漏

三、为什么工业上位机必须用WeakReferenceMessenger?

场景 强引用Messenger WeakReferenceMessenger(推荐)
内存泄漏 极易发生:注册后忘记Unregister,VM永远无法回收 几乎不会:GC自动清理无引用的VM
设备长时间运行 内存越用越多,最终程序崩溃 内存稳定,适合7×24小时工控场景
多VM解耦 解耦但留内存隐患 彻底解耦+内存安全

四、代码级对比(一眼看懂)

❌ 强引用(危险,工控禁用)
csharp# 复制代码
### 一、先搞懂「强引用」(对比才能懂弱引用)
**强引用 = 用绳子拴住设备**
比如你代码里写:
```csharp
var vm = new VisionViewModel(); // 强引用:vm变量像绳子拴住ViewModel实例

只要这根"绳子"还在(变量没置空、没出作用域),GC(垃圾回收器)就绝对不会回收这个ViewModel------哪怕界面已经关闭,实例依然占着内存,就像工人下班了但还被绳子拴在工位上,导致内存泄漏。

二、弱引用(WeakReferenceMessenger的核心)

弱引用 = 用粉笔在设备上做标记

WeakReferenceMessenger注册消息时,对接收者(比如你的VM)只做"粉笔标记",不拴绳子:

  1. 当VM该被回收时(比如窗口关闭、Dispose调用),GC能直接清理VM,不受Messenger影响
  2. 标记会自动消失,不会像强引用那样"拴住"实例导致内存泄漏

三、为什么工业上位机必须用WeakReferenceMessenger?

场景 强引用Messenger WeakReferenceMessenger(推荐)
内存泄漏 极易发生:注册后忘记Unregister,VM永远无法回收 几乎不会:GC自动清理无引用的VM
设备长时间运行 内存越用越多,最终程序崩溃 内存稳定,适合7×24小时工控场景
多VM解耦 解耦但留内存隐患 彻底解耦+内存安全

四、代码级对比(一眼看懂)

❌ 强引用(危险,工控禁用)
csharp 复制代码
// 强引用注册:Messenger死死拴住this(VM),哪怕Dispose也可能漏解注册
Messenger.Default.Register<ScanMessage>(this, (msg) => { /*处理逻辑*/ });
// 忘记Unregister → VM永远占内存 → 设备运行1天就卡顿
✅ 弱引用(安全,工控必用)
csharp 复制代码
// 弱引用注册:Messenger只标记this,不拴住
WeakReferenceMessenger.Default.Register<ScanMessage>(this, (r, msg) => { /*处理逻辑*/ });
// 即使忘记Unregister,GC也能回收VM,内存不泄漏

五、弱引用的关键特性(工控开发必记)

  1. 不影响GC回收:弱引用不会阻止GC清理目标对象,这是和强引用的核心区别
  2. 自动失效:目标对象被回收后,弱引用会自动变成"空",不会指向无效内存(避免野指针)
  3. 需配合Dispose :虽然弱引用更安全,但工控场景仍建议在Dispose中调用UnregisterAll------双重保险,避免极端场景下的泄漏

六、工业上位机实战注意点

  1. WeakReferenceMessenger不是"免死金牌"

    • 如果VM自身有强引用(比如静态变量持有、Timer事件没解绑),弱引用也救不了,依然会泄漏
    • 必须配合Dispose:Cancel CTS + Stop Timer + Dispose Halcon + UnregisterAll
  2. 调试弱引用泄漏

    用Visual Studio"内存诊断工具":

    • 捕获内存快照 → 搜索VM类名 → 查看"引用根"
    • 如果根是WeakReferenceMessenger,说明没UnregisterAll;如果是Timer/CTS,说明没释放资源

总结

  • 弱引用 = GC友好的"临时标记",强引用 = "永久绑定"
  • WeakReferenceMessenger用弱引用管理消息订阅,是工控软件长时间运行不泄漏的核心保障
  • 工控开发中:必须用WeakReferenceMessenger + 完整Dispose,二者缺一不可

// 强引用注册:Messenger死死拴住this(VM),哪怕Dispose也可能漏解注册

Messenger.Default.Register(this, (msg) => { /处理逻辑 / });

// 忘记Unregister → VM永远占内存 → 设备运行1天就卡顿

复制代码
```csharp
// 弱引用注册:Messenger只标记this,不拴住
WeakReferenceMessenger.Default.Register<ScanMessage>(this, (r, msg) => { /*处理逻辑*/ });
// 即使忘记Unregister,GC也能回收VM,内存不泄漏

五、弱引用的关键特性(工控开发必记)

  1. 不影响GC回收:弱引用不会阻止GC清理目标对象,这是和强引用的核心区别
  2. 自动失效:目标对象被回收后,弱引用会自动变成"空",不会指向无效内存(避免野指针)
  3. 需配合Dispose :虽然弱引用更安全,但工控场景仍建议在Dispose中调用UnregisterAll------双重保险,避免极端场景下的泄漏

六、工业上位机实战注意点

  1. WeakReferenceMessenger不是"免死金牌"

    • 如果VM自身有强引用(比如静态变量持有、Timer事件没解绑),弱引用也救不了,依然会泄漏
    • 必须配合Dispose:Cancel CTS + Stop Timer + Dispose Halcon + UnregisterAll
  2. 调试弱引用泄漏

    用Visual Studio"内存诊断工具":

    • 捕获内存快照 → 搜索VM类名 → 查看"引用根"
    • 如果根是WeakReferenceMessenger,说明没UnregisterAll;如果是Timer/CTS,说明没释放资源

总结

  • 弱引用 = GC友好的"临时标记",强引用 = "永久绑定"
  • WeakReferenceMessenger用弱引用管理消息订阅,是工控软件长时间运行不泄漏的核心保障
  • 工控开发中:必须用WeakReferenceMessenger + 完整Dispose,二者缺一不可

核心结论(一句话记死):

this就是当前这个VM本身 (比如你的AxisEcatVM),它通过WeakReferenceMessenger.Default(唯一中转站)"登记订阅"ScanMessage------本质是VM告诉中转站:"有ScanMessage消息时喊我,我来执行处理逻辑",中转站只做"传话筒",最终干活的还是这个VM。

用「工厂调度」再强化类比

假设你是轴控车间的班组长(当前VM,this)WeakReferenceMessenger.Default是工厂唯一的调度室(中转站单例)

  1. 订阅 = 你去调度室登记
    你走到调度室说:"凡是'启动扫描(ScanMessage)'的指令,都通知我,我来安排轴运动(处理逻辑)"。
    → 对应代码:WeakReferenceMessenger.Default.Register<ScanMessage>(this, (r, msg) => { 轴运动逻辑 });
  2. 调度室的角色
    调度室只记下来"启动扫描指令→找轴控班组长",它自己不会操作轴、不会跑逻辑,只是个"指令转发器";
  3. 发消息 = 厂长下指令
    厂长(UI层/视觉VM)给调度室说"发启动扫描指令",调度室翻登记本找到你,把指令传给你;
  4. 执行功能 = 你安排干活
    你(轴控VM)收到指令后,自己安排轴运动(调用StartScanInternal)------最终执行功能的是订阅的VM,不是中转站

代码层面的"谁在干活"(逐句标清楚):

csharp 复制代码
WeakReferenceMessenger.Default.Register<ScanMessage>(
    this, // 干活的人:当前VM(AxisEcatVM)
    (r, msg) => { 
        // 干活的内容:VM自己执行扫描逻辑
        StartScanInternal(msg.TargetX, msg.TargetY); 
    });
  • WeakReferenceMessenger.Default:只负责"记下来谁要干这个活"+"收到指令后喊人";
  • this:是真正"接活、干活"的主体;
  • 箭头函数里的逻辑:是VM要干的具体活。

为什么要绕这一层(工控场景的核心价值):

如果没有中转站,UI按钮要直接调用AxisEcatVM的StartScanInternal------意味着UI层必须"认识"AxisEcatVM(引用它),一旦你换了轴控模块(比如从EtherCAT换成Modbus),UI层代码也要改;

有了中转站后:

  • UI层只需要"给调度室发指令",不用管谁来干活;
  • 轴控VM只需要"在调度室登记接活",不用管谁发指令;
  • 后续换轴控模块,只需要新模块去调度室登记"接启动扫描的活",UI层一行代码都不用改------这就是工控软件需要的"解耦"。

最终简化记忆:

  • 中转站(Default)= 调度室(只传指令,不干活);
  • 订阅 = 班组长(VM,this)去调度室登记"接某类活";
  • 执行 = 班组长收到指令后,自己安排干活。

你的理解已经完全到位了,这个逻辑是MVVM通信的核心,也是工控软件模块化开发的关键~

r和msg 极简解读(工控场景版)

在这段注册代码里,rmsglambda表达式的两个参数,对应"订阅消息后收到通知时的输入数据",用工厂调度的类比能一眼懂:

csharp 复制代码
WeakReferenceMessenger.Default.Register<ScanMessage>(
    this, 
    (r, msg) => { /*处理逻辑*/ }  // 重点看这两个参数
);
1. msg(核心参数,必须用)
  • 含义msg = 你收到的「具体指令内容」(就是发送方发来的ScanMessage对象)

  • 类比:调度室转给你的"启动扫描指令单",上面写着目标X/Y坐标、曝光时间等具体参数

  • 实际用途 :在处理逻辑里取指令参数,比如:

    csharp 复制代码
    (r, msg) => { 
        // 从msg里拿指令参数,执行扫描
        StartScanInternal(msg.TargetX, msg.TargetY, msg.ExposureMs); 
    }
  • 关键msg的类型就是你注册时<>里的ScanMessage,只能拿到这个类型的消息数据。

2. r(可选参数,几乎不用)
  • 含义r = 订阅消息的"接收者本身"(就是你写的this,当前VM实例)

  • 类比:调度室在给你指令单时,顺便告诉你"这份指令是给XX班组长(你自己)的"

  • 实际用途 :99%的工控场景用不到,因为rthis是同一个对象,比如:

    csharp 复制代码
    (r, msg) => { 
        // r == this(当前AxisEcatVM实例),完全等价
        r.StartScanInternal(msg.TargetX, msg.TargetY); 
        this.StartScanInternal(msg.TargetX, msg.TargetY); 
    }
  • 简化写法 :既然用不到r,可以直接省略,写成:

    csharp 复制代码
    WeakReferenceMessenger.Default.Register<ScanMessage>(
        this, 
        (_, msg) => { // 下划线表示"这个参数不用,只是占位"
            StartScanInternal(msg.TargetX, msg.TargetY); 
        }
    );
核心总结
参数 本质 用途
msg 发来的ScanMessage消息对象 取指令参数(X/Y坐标、曝光时间等),核心必用
r 订阅消息的VM本身(this) 几乎不用,可下划线占位省略

简单说:=只需要关注msg------它是发送方传的"指令详情",r只是"这是给你的指令"。

OperationCanceledException 核心理解

1. 一句话定义

操作被取消异常这不是程序报错!不是BUG!

是你调用 _cts.Cancel()(急停/停止任务)时,.NET 专门抛出的正常终止信号,意思是:

后台任务(轴扫描、图像采集)主动被你取消了,不是崩溃、不是故障,是正常停止!


2. 对应你的工控场景

后台任务:

  • 任务 = 轴持续扫描 / Halcon图像采集
  • _cts.Cancel() = 按下急停按钮 / 点击停止扫描
  • OperationCanceledException = 任务收到急停信号,优雅停下时发出的通知

3. 什么时候会触发?

只要你用了 CancellationToken,这3个地方会自动抛这个异常:

  1. token.ThrowIfCancellationRequested();
  2. await Task.Delay(200, token);
  3. 异步任务中检测到取消信号时

4. 为什么要捕获它?(关键!)

如果不捕获:

  • 正常的停止任务会被程序当成「崩溃报错」,弹出错误、记录异常日志;
  • 你明明是手动停止扫描,结果程序红屏报警,不符合工控逻辑。

正确逻辑

捕获这个异常 → 标记任务正常取消 → 不报错、只记录日志。


写法

csharp 复制代码
try
{
    // 你的后台扫描任务(无限循环读取轴坐标)
    while (!token.IsCancellationRequested)
    {
        // 读取轴位置、刷新UI
        await ReadAxisStatusAsync(token);
        await Task.Delay(200, token);
    }
}
// 专门捕获:任务被取消(正常停止)
catch (OperationCanceledException ex)
{
    // 工控逻辑:这里只写日志,不报错!
    Console.WriteLine("轴扫描任务 → 已正常停止(手动取消)");
}
// 捕获真正的错误(比如轴通信失败)
catch (Exception ex)
{
    // 真正的故障:报警、弹窗
    Console.WriteLine($"轴扫描故障:{ex.Message}");
}

代码 动作 结果
_cts = new CTS() 创建取消令牌 准备控制任务
_cts.Cancel() 发送停止信号(急停) 触发取消
OperationCanceledException 任务响应停止 正常终止,不是报错
UnregisterAll 注销消息 彻底清理

最终总结

  1. OperationCanceledException = 任务被手动取消的正常信号(急停、停止扫描);
  2. 不是异常故障,千万不要当成程序错误;
  3. 捕获它 = 区分「我主动停的 」和「程序坏了」;
  4. 是你工控上位机急停功能的标准配套异常。
bash 复制代码
WpfApp6/MainWindow.xaml:26:            <TextBlock Text="{Binding MainVM.
Status}" FontSize="16" FontWeight="Bold" Foreground="blue"/>
WpfApp6/MainWindow.xaml:30:        <TextBlock Grid.Row="0" Grid.Column="
1" Text="{Binding MainVM.BusStatus}"
WpfApp6/MainWindow.xaml:46:                               Text="{Binding
 MainVM.ExposureMs,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:47:                             IsEnabled="{Bind
ing MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:50:                             Text="{Binding M
ainVM.ScanSpeed,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:51:                             IsEnabled="{Bind
ing MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:54:                             :...skipping...
WpfApp6/MainWindow.xaml:26:            <TextBlock Text="{Binding MainVM.Status}" FontSize="16" FontWeight="Bold" Foreground="blue"/>
WpfApp6/MainWindow.xaml:30:        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MainVM.BusStatus}"
WpfApp6/MainWindow.xaml:46:                               Text="{Binding MainVM.ExposureMs,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:47:                             IsEnabled="{Binding MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:50:                             Text="{Binding MainVM.ScanSpeed,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:51:                             IsEnabled="{Binding MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:54:                             Text="{Binding MainVM.TargetX,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:55:                             IsEnabled="{Bind
ing MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:60:                     Value="{Binding MainVM.R
ate}" Minimum="0"  Maximum="1"
WpfApp6/MainWindow.xaml:63:                 ItemsSource="{Binding MainVM
.LogList}" Height="auto" Margin="10">
WpfApp6/MainWindow.xaml:78:            <Button Command="{Binding MainVM.
StartScanCommand}" Content="点位扫描"
WpfApp6/MainWindow.xaml:80:            <Button Command="{Binding MainVM.
StartScanCommand}" Content="连续扫描"
WpfApp6/MainWindow.xaml:82:            <Button Command="{Binding MainVM.
StopScanCommand}" Content="停止"
WpfApp6/MainWindow.xaml:28:            <TextBlock Text="{Binding AxisVM.
AxisStatusText}" FontSize="14" Foreground="DarkGreen"/>
Archive/legacy/MainViewModel.cs:13:    public class MainViewModel:ViewMo
delBase
Archive/legacy/MainViewModel.cs:18:        public MainViewModel(IXYScanA
xis scanAxis, IEtherCATMaster ecatMaster, ILogService logService)
WpfApp6/MainWindow.xaml.cs:28:        // private readonly MainViewModel_viewModel;

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "WeakReferenceMessenger.Default.Register<" -- "*.cs" || tr
ue
git grep -n "WeakReferenceMessenger.Default.Send" -- "*.cs" || true
WpfApp6/ViewModel/AxisEcatVM.cs:57:            WeakReferenceMessenger.De
fault.Register<StartScanMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/AxisEcatVM.cs:63:            WeakReferenceMessenger.Default.Register<StopScanMessage>(this, (recipient, msg) => Stop());
WpfApp6/ViewModel/AxisEcatVM.cs:64:            WeakReferenceMessenger.De
fault.Register<UpdateTargetMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:101:            WeakReferenceMessenger.
Default.Register<LogMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:105:            WeakReferenceMessenger.
Default.Register<BusStatusMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:109:            WeakReferenceMessenger.
Default.Register<ScanProgressMessage>(this, (recipient, msg) => {
WpfApp6/ViewModel/XrayImageVM.cs:25:            WeakReferenceMessenger.D
efault.Register<AxisPositionReadyMessage>(this, (r, m) =>
WpfApp6/ViewModel/XrayImageVM.cs:29:            WeakReferenceMessenger.Default.Register<StopScanMessage>(this, (r, m) =>
WpfApp6/Services/LogService.cs:32:                            CommunityT
oolkit.Mvvm.Messaging.WeakReferenceMessenger.Default.Send(new Messages.AppMessages.LogMessage($"LogService" +
WpfApp6/Services/LogService.cs:42:                    CommunityToolkit.Mvvm.Messaging.WeakReferenceMessenger.Default.Send(new Messages.AppMessages.LogMessage($"LogService" +
WpfApp6/ViewModel/AxisEcatVM.cs:59:                WeakReferenceMessenger.Default.Send(new LogMessage($"[DEBUG] AxisEcatVM收到 StartScanMessage:NewX={msg.TargetX}" +
WpfApp6/ViewModel/AxisEcatVM.cs:66:                WeakReferenceMessenge
r.Default.Send(new LogMessage($"[DEBUG] AxisEcatVM收到 UpdateTarget:NewX={msg.NewTargetX}"));
WpfApp6/ViewModel/AxisEcatVM.cs:81:                        WeakReferenceMessenger.Default.Send(new LogMessage("已有扫描任务运行,忽略新的请求"));
WpfApp6/ViewModel/AxisEcatVM.cs:102:                    WeakReferenceMessenger.Default.Send(new LogMessage($"扫描任务异常:{ex?.Message}"));
WpfApp6/ViewModel/AxisEcatVM.cs:116:                    WeakReferenceMessenger.Default.Send(new BusStatusMessage(busLog));
WpfApp6/ViewModel/AxisEcatVM.cs:119:                    WeakReferenceMes
senger.Default.Send(new ScanProgressMessage(progress));
WpfApp6/ViewModel/AxisEcatVM.cs:122:                        WeakReferenceMessenger.Default.Send(new LogMessage($"[限位报警]X坐标超出阈值({_scanAxis.Posx:F1})"));
WpfApp6/ViewModel/AxisEcatVM.cs:142:                WeakReferenceMessenger.Default.Send(new LogMessage($"轴移动至 X:{targetX:f1} Y:{targetY:F1}"
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$
csharp 复制代码
WpfApp6/MainWindow.xaml:26:            <TextBlock Text="{Binding MainVM.
Status}" FontSize="16" FontWeight="Bold" Foreground="blue"/>
WpfApp6/MainWindow.xaml:30:        <TextBlock Grid.Row="0" Grid.Column="
1" Text="{Binding MainVM.BusStatus}"
WpfApp6/MainWindow.xaml:46:                               Text="{Binding
 MainVM.ExposureMs,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:47:                             IsEnabled="{Bind
ing MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:50:                             Text="{Binding M
ainVM.ScanSpeed,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:51:                             IsEnabled="{Bind
ing MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:54:                             :...skipping...
WpfApp6/MainWindow.xaml:26:            <TextBlock Text="{Binding MainVM.Status}" FontSize="16" FontWeight="Bold" Foreground="blue"/>
WpfApp6/MainWindow.xaml:30:        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MainVM.BusStatus}"
WpfApp6/MainWindow.xaml:46:                               Text="{Binding MainVM.ExposureMs,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:47:                             IsEnabled="{Binding MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:50:                             Text="{Binding MainVM.ScanSpeed,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:51:                             IsEnabled="{Binding MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:54:                             Text="{Binding MainVM.TargetX,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
WpfApp6/MainWindow.xaml:55:                             IsEnabled="{Bind
ing MainVM.CanEditParam}"/>
WpfApp6/MainWindow.xaml:60:                     Value="{Binding MainVM.R
ate}" Minimum="0"  Maximum="1"
WpfApp6/MainWindow.xaml:63:                 ItemsSource="{Binding MainVM
.LogList}" Height="auto" Margin="10">
WpfApp6/MainWindow.xaml:78:            <Button Command="{Binding MainVM.
StartScanCommand}" Content="点位扫描"
WpfApp6/MainWindow.xaml:80:            <Button Command="{Binding MainVM.
StartScanCommand}" Content="连续扫描"
WpfApp6/MainWindow.xaml:82:            <Button Command="{Binding MainVM.
StopScanCommand}" Content="停止"
WpfApp6/MainWindow.xaml:28:            <TextBlock Text="{Binding AxisVM.
AxisStatusText}" FontSize="14" Foreground="DarkGreen"/>
Archive/legacy/MainViewModel.cs:13:    public class MainViewModel:ViewMo
delBase
Archive/legacy/MainViewModel.cs:18:        public MainViewModel(IXYScanA
xis scanAxis, IEtherCATMaster ecatMaster, ILogService logService)
WpfApp6/MainWindow.xaml.cs:28:        // private readonly MainViewModel _viewModel;

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "WeakReferenceMessenger.Default.Register<" -- "*.cs" || tr
ue
git grep -n "WeakReferenceMessenger.Default.Send" -- "*.cs" || true
WpfApp6/ViewModel/AxisEcatVM.cs:57:            WeakReferenceMessenger.De
fault.Register<StartScanMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/AxisEcatVM.cs:63:            WeakReferenceMessenger.Default.Register<StopScanMessage>(this, (recipient, msg) => Stop());
WpfApp6/ViewModel/AxisEcatVM.cs:64:            WeakReferenceMessenger.De
fault.Register<UpdateTargetMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:101:            WeakReferenceMessenger.
Default.Register<LogMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:105:            WeakReferenceMessenger.
Default.Register<BusStatusMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:109:            WeakReferenceMessenger.
Default.Register<ScanProgressMessage>(this, (recipient, msg) => {
WpfApp6/ViewModel/XrayImageVM.cs:25:            WeakReferenceMessenger.D
efault.Register<AxisPositionReadyMessage>(this, (r, m) =>
WpfApp6/ViewModel/XrayImageVM.cs:29:            WeakReferenceMessenger.Default.Register<StopScanMessage>(this, (r, m) =>
WpfApp6/Services/LogService.cs:32:                            CommunityT
oolkit.Mvvm.Messaging.WeakReferenceMessenger.Default.Send(new Messages.AppMessages.LogMessage($"LogService" +
WpfApp6/Services/LogService.cs:42:                    CommunityToolkit.Mvvm.Messaging.WeakReferenceMessenger.Default.Send(new Messages.AppMessages.LogMessage($"LogService" +
WpfApp6/ViewModel/AxisEcatVM.cs:59:                WeakReferenceMessenger.Default.Send(new LogMessage($"[DEBUG] AxisEcatVM收到 StartScanMessage:NewX={msg.TargetX}" +
WpfApp6/ViewModel/AxisEcatVM.cs:66:                WeakReferenceMessenge
r.Default.Send(new LogMessage($"[DEBUG] AxisEcatVM收到 UpdateTarget:NewX={msg.NewTargetX}"));
WpfApp6/ViewModel/AxisEcatVM.cs:81:                        WeakReferenceMessenger.Default.Send(new LogMessage("已有扫描任务运行,忽略新的请求"))
;
WpfApp6/ViewModel/AxisEcatVM.cs:102:                    WeakReferenceMes
senger.Default.Send(new LogMessage($"扫描任务异常:{ex?.Message}"));
WpfApp6/ViewModel/AxisEcatVM.cs:116:                    WeakReferenceMes
senger.Default.Send(new BusStatusMessage(busLog));
WpfApp6/ViewModel/AxisEcatVM.cs:119:                    WeakReferenceMes
senger.Default.Send(new ScanProgressMessage(progress));
WpfApp6/ViewModel/AxisEcatVM.cs:122:                        WeakReferenceMessenger.Default.Send(new LogMessage($"[限位报警]X坐标超出阈值({_scanA
xis.Posx:F1})"));
WpfApp6/ViewModel/AxisEcatVM.cs:142:                WeakReferenceMesseng
er.Default.Send(new LogMessage($"轴移动至 X:{targetX:f1} Y:{targetY:F1}"
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "new CancellationTokenSource" -- "*.cs" || true
git grep -n -e "\.Wait(" -- "*.cs" || true
git grep -n -e "\.Result" -- "*.cs" || true
git grep -n "Dispatcher.Invoke(" -- "*.cs" || true
git grep -n "Dispatcher.InvokeAsync(" -- "*.cs" || true
git grep -n "async void" -- "*.cs" || true
git grep -n "WeakReferenceMessenger.Default.Register<" -- "*.cs" || true
git grep -n "UnregisterAll(this)" -- "*.cs" || true
git grep -n "Task.Run(" -- "*.cs" || true
git grep -n "Task.Delay(" -- "*.cs" || true
Archive/Legacy/AxisControlViewModel.cs:64:            _cts = new Cancell
ationTokenSource();
WpfApp6/ViewModel/AxisEcatVM.cs:93:                _cts = new Cancellati
onTokenSource();
WpfApp6/ViewModel/AxisEcatVM.cs:136:                //_cts = new Cancell
ationTokenSource();
WpfApp6/EtherCATMaster.cs:41:                //Task.Delay(1000).Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:88:                    //    wait.Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:207:                    // Task.Delay(100).Wait(); // 等待任务响应取消
WpfApp6/ViewModel/AxisEcatVM.cs:230:                    //wait.Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:292:                    wait.Wait();
WpfApp6/EtherCATMaster.cs:28:                Application.Current.Dispatcher.InvokeAsync(() => BusLog = "EtherCAT总线初始化中...");
WpfApp6/EtherCATMaster.cs:30:                Application.Current.Dispatcher.InvokeAsync(() =>
WpfApp6/EtherCATMaster.cs:39:                Application.Current.Dispatcher.InvokeAsync(() => BusLog = $"总线初始化失败:{ex.Message}");
WpfApp6/ViewModel/AxisEcatVM.cs:111:                System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
WpfApp6/ViewModel/XrayImageVM.cs:41:                    Application.Curr
ent.Dispatcher.InvokeAsync(() =>
WpfApp6/ViewModel/XrayImageVM.cs:73:                Application.Current.Dispatcher.InvokeAsync(() =>
WpfApp6/ViewModel/XrayImageVM.cs:118:                    Application.Current.Dispatcher.InvokeAsync(() => ClearWindow());
WpfApp6/ViewModel/AxisEcatVM.cs:57:            WeakReferenceMessenger.Default.Register<StartScanMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/AxisEcatVM.cs:63:            WeakReferenceMessenger.Default.Register<StopScanMessage>(this, (recipient, msg) => Stop());
WpfApp6/ViewModel/AxisEcatVM.cs:64:            WeakReferenceMessenger.De
fault.Register<UpdateTargetMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:101:            WeakReferenceMessenger.Default.Register<LogMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:105:            WeakReferenceMessenger.
Default.Register<BusStatusMessage>(this, (recipient, msg) =>
WpfApp6/ViewModel/MainShellVM.cs:109:            WeakReferenceMessenger.Default.Register<ScanProgressMessage>(this, (recipient, msg) => {
WpfApp6/ViewModel/XrayImageVM.cs:25:            WeakReferenceMessenger.D
efault.Register<AxisPositionReadyMessage>(this, (r, m) =>
WpfApp6/ViewModel/XrayImageVM.cs:29:            WeakReferenceMessenger.D
efault.Register<StopScanMessage>(this, (r, m) =>
WpfApp6/ViewModel/AxisEcatVM.cs:303:            try { WeakReferenceMesse
nger.Default.UnregisterAll(this); }catch { }
WpfApp6/ViewModel/MainShellVM.cs:148:            try { WeakReferenceMess
enger.Default.UnregisterAll(this); }catch { }
WpfApp6/ViewModel/XrayImageVM.cs:127:            try { WeakReferenceMess
enger.Default.UnregisterAll(this); }catch { }
WpfApp6/ViewModel/XrayImageVM.cs:129:            //WeakReferenceMessenger.Default.UnregisterAll(this);
WpfApp6/ViewModel/AxisEcatVM.cs:97:                _runningScanTask = Task.Run(() => RunScanTask(tx, ty, exposureMs, _cts.Token));
WpfApp6/ViewModel/AxisEcatVM.cs:214:                    Task.Run(async () =>
Archive/Legacy/AxisControlViewModel.cs:57:            await Task.Delay(100);
WpfApp6/EtherCATMaster.cs:41:                //Task.Delay(1000).Wait();
WpfApp6/EtherCATMaster.cs:56:            await Task.Delay(500);
WpfApp6/ViewModel/AxisEcatVM.cs:87:                    //    var wait = Task.WhenAny(_runningScanTask, Task.Delay(500));
WpfApp6/ViewModel/AxisEcatVM.cs:207:                    // Task.Delay(100).Wait(); // 等待任务响应取消
WpfApp6/ViewModel/AxisEcatVM.cs:218:                            var comp
leted = await Task.WhenAny(runningTask, Task.Delay(500)).ConfigureAwait(
false);
WpfApp6/ViewModel/AxisEcatVM.cs:229:                    //var wait = Task.WhenAny(runningTask, Task.Delay(500));
WpfApp6/ViewModel/AxisEcatVM.cs:291:                    var wait = Task.WhenAny(runningTask, Task.Delay(1000));
WpfApp6/XYScanAxis.cs:48:                await Task.Delay(1500, token);
WpfApp6/XYScanAxis.cs:66:                await Task.Delay(1000,token);
WpfApp6/XYScanAxis.cs:89:            //        await Task.Delay(200,token);

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ •     git grep -n -e ".Wait(" -- "*.cs" || true
•       git grep -n -e ".Result" -- "*.cs" || true
bash: •: command not found
bash: •: command not found

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n -e ".Wait(" -- "*.cs" || true git grep -n -e ".Result" --
"*.cs" || true
WpfApp6/EtherCATMaster.cs:41:                //Task.Delay(1000).Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:88:                    //    wait.Wait()
;
WpfApp6/ViewModel/AxisEcatVM.cs:207:                    // Task.Delay(10
0).Wait(); // 等待任务响应取消
WpfApp6/ViewModel/AxisEcatVM.cs:230:                    //wait.Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:292:                    wait.Wait();

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n -e "\.Wait(" -- "*.cs" || true
git grep -n -e "\.Result" -- "*.cs" || true
git grep -n "new CancellationTokenSource" -- "*.cs" || true
git grep -n "Dispatcher.Invoke(" -- "*.cs" || true
git grep -n "async void" -- "*.cs" || true
WpfApp6/EtherCATMaster.cs:41:                //Task.Delay(1000).Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:88:                    //    wait.Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:207:                    // Task.Delay(100).Wait(); // 等待任务响应取消
WpfApp6/ViewModel/AxisEcatVM.cs:230:                    //wait.Wait();
WpfApp6/ViewModel/AxisEcatVM.cs:291:                    Task.Run(async (
)=> await Task.WhenAny(runningTask, Task.Delay(1000))).Wait();
WpfApp6/ViewModel/XrayImageVM.cs:120:                    try { op.Task.Wait(300); } catch { }
Archive/Legacy/AxisControlViewModel.cs:64:            _cts = new CancellationTokenSource();
WpfApp6/ViewModel/AxisEcatVM.cs:93:                _cts = new CancellationTokenSource();
WpfApp6/ViewModel/AxisEcatVM.cs:136:                //_cts = new CancellationTokenSource();

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout  feature/bug
M       WpfApp6/ViewModel/AxisEcatVM.cs
M       WpfApp6/ViewModel/MainShellVM.cs
M       WpfApp6/ViewModel/XrayImageVM.cs
Switched to branch 'feature/bug'

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .

11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "       Start 在 StartScanInternal 创建 CTS,保存 _runni
ngScanTask;RunScanTask 的 finally 负责 Dispose CTS。
> "
[feature/bug d809288]   Start 在 StartScanInternal 创建 CTS,保存 _runningScanTask;RunScanTask 的 finally 负责 Dispose CTS。
 3 files changed, 32 insertions(+), 20 deletions(-)

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git checkout master
Switched to branch 'master'

11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge feature/bug
Updating 9ac80b7..d809288
Fast-forward
 WpfApp6/ViewModel/AxisEcatVM.cs  | 27 ++++++++++++---------------
 WpfApp6/ViewModel/MainShellVM.cs | 21 +++++++++++++++++----
 WpfApp6/ViewModel/XrayImageVM.cs |  4 +++-
 3 files changed, 32 insertions(+), 20 deletions(-)

11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout feature/bug
M       WpfApp6/ViewModel/XrayImageVM.cs
Switched to branch 'feature/bug'

11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .

11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "Xray 图像与轴到位不同步 增加防抖"
[feature/bug b232996] Xray 图像与轴到位不同步 增加防抖
 1 file changed, 127 insertions(+), 40 deletions(-)

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "Xray 图像与轴到位不同步 增加防抖"
On branch feature/bug
nothing to commit, working tree clean

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git checkout master
Switched to branch 'master'

11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge feature/bug
Updating d809288..b232996
Fast-forward
 WpfApp6/ViewModel/XrayImageVM.cs | 167 +++++++++++++++++++++++++++++----------
 1 file changed, 127 insertions(+), 40 deletions(-)

11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout feature/bug
M       WpfApp6/Messages/AppMessages.cs
M       WpfApp6/ViewModel/AxisEcatVM.cs
M       WpfApp6/ViewModel/MainShellVM.cs
M       WpfApp6/ViewModel/XrayImageVM.cs
M       WpfApp6/XYScanAxis.cs
Switched to branch 'feature/bug'

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "还是不同步问题,现在好了 加了锁 在xysModel中"
[feature/bug b6fa6ec] 还是不同步问题,现在好了 加了锁 在xysModel中
 5 files changed, 184 insertions(+), 37 deletions(-)

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .

11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "还是不同步问题,现在好了 加了锁 在xysModel中"
On branch feature/bug
nothing to commit, working tree clean

11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git checkout master
Switched to branch 'master'

11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge feature/bug
Updating b232996..b6fa6ec
Fast-forward
 WpfApp6/Messages/AppMessages.cs  |  29 +++++++++--
 WpfApp6/ViewModel/AxisEcatVM.cs  | 101 +++++++++++++++++++++++++++++++++------
 WpfApp6/ViewModel/MainShellVM.cs |  13 +++--
 WpfApp6/ViewModel/XrayImageVM.cs |  20 ++++++++
 WpfApp6/XYScanAxis.cs            |  58 ++++++++++++++++------
 5 files changed, 184 insertions(+), 37 deletions(-)

11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$
相关推荐
搞科研的小刘选手3 天前
【高届数计算机方向会议】第七届计算机视觉与数据挖掘国际学术会议(ICCVDM 2026)
人工智能·算法·计算机·数据挖掘·软件工程·视觉·信息
czhc11400756634 天前
6.3 # GigE Vision:半导体X光检测的高速图像传输协议
视觉
czhc11400756636 天前
5.30:多VM解耦 X光检测(曝光)(func<CancellationToken,Task> scanLogic)
视觉
czhc11400756636 天前
6.1EtherCAT工业架构:软主站,分布式时钟DC,PDO实时通信
视觉·运控
czhc11400756639 天前
529: XYScanAxis()类
mvvm·视觉·运控
czhc114007566310 天前
528:Halcon图像控件 启动轴状态实时监控
mvvm·视觉·运控
czhc114007566311 天前
5.27 :工业设备检测模式与安全防护详解
安全·mvvm·视觉·运控
czhc114007566313 天前
525:检测完善
mvvm·视觉
学不懂飞行器13 天前
【电赛保姆级教程】电赛视觉怎么选?怎么调?从OpenMV到边缘计算硬核避坑指南(附高鲁棒通信源码)
人工智能·stm32·边缘计算·电赛·视觉