⏹️ _cts = new CancellationTokenSource(); 核心解析(工业上位机专用)
1. 本质定位
CancellationTokenSource = 异步任务的"停工令牌发放器" ,专门用于向EtherCAT扫描、Halcon图像采集、轴运动控制等长时间运行任务发送协作式取消信号,是工业上位机异步编程的"安全终止开关"。
2. 核心作用(上位机场景)
| 功能 | 工业场景应用 | 代码示例 |
|---|---|---|
| 任务取消 | 紧急停止EtherCAT总线扫描、中断Halcon图像分析 | _cts.Cancel(); |
| 超时保护 | 防止轴定位/PLC通信无限阻塞 | _cts.CancelAfter(5000);(5秒超时) |
| 资源释放 | 配合Dispose()清理任务句柄、关闭硬件连接 |
_cts?.Dispose(); |
| 多任务协同 | 统一控制扫描+运动+图像采集三大任务同步终止 | 共享_cts.Token给所有任务 |
3. 基础用法(上位机极简模板)
csharp
// 1. 初始化(在ViewModel构造或Start按钮事件中)
_cts = new CancellationTokenSource();
CancellationToken token = _cts.Token;
// 2. 启动后台任务(如EtherCAT扫描)
_runningScanTask = Task.Run(async () =>
{
while (!token.IsCancellationRequested) // 循环检查取消信号
{
token.ThrowIfCancellationRequested(); // 快速失败机制
// 工业操作:读取轴坐标、总线状态
var axisPos = await _ecatMaster.ReadAxisPositionAsync(token);
// 切UI线程更新绑定属性
await Application.Current.Dispatcher.InvokeAsync(() =>
{
AxisStatusText = $"轴位置: {axisPos}";
});
await Task.Delay(200, token); // 200ms轮询,支持取消
}
}, token);
4. 工业级资源释放规范(必做,防内存泄漏)
CancellationTokenSource持有内核对象,不释放会导致线程句柄泄漏、硬件资源无法回收,必须在以下时机处理:
标准Dispose实现
csharp
private void Dispose(bool disposing)
{
lock (_disposeLock)
{
if (_isDisposed) return;
_isDisposed = true;
// 1. 发送取消信号(先软停止,再硬释放)
_cts?.Cancel();
// 2. 等待任务优雅退出(可选,根据业务需求)
_runningScanTask?.Wait(1000); // 最多等1秒
// 3. 释放令牌源(关键!)
_cts?.Dispose();
_cts = null; // 置空避免重复释放
// 其他资源释放...
_statusTimer?.Dispose();
_ecatMaster?.Dispose();
}
}
安全重置模式(任务重启时)
csharp
// 停止当前任务并重启
private void RestartScan()
{
// 先清理旧令牌
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
// 创建新令牌源并启动任务
_cts = new CancellationTokenSource();
StartEthercatScan(_cts.Token);
}
5. 上位机场景避坑指南
❌ 常见错误
- 只Cancel不Dispose:导致内核对象泄漏,长时间运行后程序崩溃
- 未检查取消状态 :任务无视
IsCancellationRequested,无法正常终止 - async void中使用:异常无法捕获,取消信号可能失效
- 静态持有:导致对象无法被GC回收,内存泄漏
✅ 正确实践
-
配合using声明(局部任务):
csharpusing var cts = new CancellationTokenSource(); await RunSingleAxisMoveAsync(cts.Token); // 自动Dispose -
取消异常处理:
csharptry { await _runningScanTask; } catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token) { // 正常取消,记录日志不报错 Log.Info("EtherCAT扫描已被用户终止"); } -
多任务联动取消(如扫描+图像采集):
csharp// 关联多个令牌源,任一取消则全部取消 var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( _scanCts.Token, _visionCts.Token);
6. 与你的上位机代码结合点
在你的EtherCAT+Halcon项目中,_cts应:
- 控制
_runningScanTask后台扫描任务的启停 - 配合
_statusTimer实现"定时扫描+紧急停止"双保险 - 在
Dispose中与_ecatMaster、_scanAxis等硬件资源一起释放 - 提供UI"停止扫描"按钮的事件响应:
private void OnStopButtonClick() => _cts?.Cancel();
总结
_cts = new CancellationTokenSource();是工业上位机异步任务的"安全大脑",核心价值是实现"可控、优雅、安全"的任务终止 ,避免强制终止导致的总线异常、硬件损坏和内存泄漏。记住三个关键字:创建→使用→释放,三者缺一不可。
Timer

Messenger 与注册:(工业上位机视角)
一、Messenger 是什么?(3个核心类比)
- 对讲机总台:不同VM/模块是不同的工人,Messenger是工地中央的对讲机总台,负责转发消息,避免工人之间互相拉扯线缆(直接引用)
- 工厂公告栏:一个地方贴通知(发送消息),多个部门(接收者)看通知,无需互相认识
- 公交车调度中心:调度中心(Messenger)统一指挥所有车辆(对象),车辆只需收听自己的线路指令
核心作用 :在MVVM架构中实现解耦通信------ViewModel之间、ViewModel与View之间无需互相引用,通过消息传递数据或指令,降低耦合度,方便维护与重构
二、什么是注册(Register)?
注册 = 订阅消息,就像:
- 工人到总台登记:"我要听'生产线启动'的指令"
- 部门到公告栏贴纸条:"这个通知我要"
- 公交车到调度中心报备:"我负责3号线,只听3号线指令"
代码里的注册:告诉Messenger"我要接收XX类型的消息,收到后执行XX操作"
csharp
// 标准注册写法(WeakReferenceMessenger)
WeakReferenceMessenger.Default.Register<VisionCommandMessage>(
this, // 接收者(我)
(recipient, message) => // 收到消息后的处理方法
{
// 业务逻辑:比如执行视觉检测
ProcessVisionCommand(message.Command);
});
三、关键操作速览(工业上位机常用)
| 操作 | 代码 | 类比 | 用途 |
|---|---|---|---|
| 注册 | Register<TMessage>(this, 处理方法) |
登记收听频道 | 订阅特定消息 |
| 发送 | Send(new TMessage(数据)) |
总台喊话 | 发布消息 |
| 解注册 | UnregisterAll(this) |
取消登记 | 停止接收消息,防止内存泄漏 |
四、为什么必须在Dispose中UnregisterAll?
想象工人下班不注销对讲机:
- 总台还会给他发指令(消息回调),但他已经不在岗(对象已该被回收)
- 占用频道资源,导致内存泄漏(GC无法回收)
修复要求 :所有注册过Messenger的类必须实现IDisposable,在Dispose中调用WeakReferenceMessenger.Default.UnregisterAll(this),彻底切断消息订阅
所有注册 Messenger 的 VM 必须实现 IDisposable,确保对称释放
释放顺序严格遵循:Cancel→Dispose CTS → Stop Timer → Halcon 资源(UI 线程)→ UnregisterAll 消息 → 标记已释放
Halcon 对象必须在 UI 线程释放,避免跨线程异常
双重释放防护:标记_disposed,防止重复调用
| 步骤 | 操作 | 目的 | 安全要点 |
|---|---|---|---|
| 1 | CTS.Cancel() | 通知所有关联任务立即停止 | 必须先 Cancel 再 Dispose,避免任务无法响应 |
| 2 | CTS.Dispose | 释放 CTS 资源 | 防止句柄泄漏 |
| 3 | Timer.Stop() | 停止定时器触发 | 避免 Tick 事件持续执行 |
| 4 | Timer 事件解绑定 | 切断引用,允许 GC 回收 | 防止内存泄漏 |
| 5 | Halcon.Close() | 关闭窗口 / 句柄 | UI 线程执行,避免跨线程异常 |
| 6 | Halcon.Dispose() | 释放 Halcon 非托管资源 | 配对创建与释放,防止内存泄漏 |
| 7 | UnregisterAll() | 取消所有消息订阅 | 避免消息回调导致的内存泄漏 |
五、WeakReferenceMessenger vs 普通事件(上位机开发重点)
| 对比项 | WeakReferenceMessenger | 普通事件 | 工业场景选择 |
|---|---|---|---|
| 引用类型 | 弱引用(不阻止GC回收) | 强引用(易内存泄漏) | 选Messenger |
| 跨模块 | 无需引用,直接通信 | 需持有对方实例 | 选Messenger |
| 解耦度 | 高(适合大型设备软件) | 低(适合简单UI交互) | 选Messenger |
| 资源管理 | 需手动UnregisterAll | 需手动注销事件 | 两者都要做 |
六、上位机开发中的典型应用场景
- 设备状态通知:PLC通信模块发送"连接成功"消息,多个VM同时更新UI状态
- 视觉检测结果:Halcon处理完成后发送结果消息,UI显示+数据记录+报警判断
- 操作权限控制:登录模块发送"权限变更"消息,所有功能模块更新按钮状态
七、极简使用步骤(直接套用)
-
定义消息类(传递的数据容器)
csharppublic class VisionResultMessage // 视觉检测结果消息 { public bool IsPass { get; } public double DefectArea { get; } public VisionResultMessage(bool isPass, double defectArea) { IsPass = isPass; DefectArea = defectArea; } } -
接收方注册(VM构造函数中)
csharpWeakReferenceMessenger.Default.Register<VisionResultMessage>( this, (r, m) => UpdateVisionResult(m.IsPass, m.DefectArea)); -
发送方发消息(Halcon处理完成后)
csharpWeakReferenceMessenger.Default.Send( new VisionResultMessage(isPass: true, defectArea: 0.5)); -
Dispose中解注册(必须)
csharpprotected override void CleanupMessenger() { WeakReferenceMessenger.Default.UnregisterAll(this); }
总结
- Messenger = 工业软件中的消息中转站,负责不同模块间的解耦通信
- 注册 = 订阅消息,告诉中转站"我要接收什么消息,怎么处理"
- 上位机开发必须:注册后在Dispose中对称解注册,防止内存泄漏
csharp
11400@▒▒ MINGW64 ~
$ git status
fatal: not a git repository (or any of the parent directories): .git
11400@ MINGW64 ~
$ cd /d/vsprogram/WpfApp6
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: WpfApp6/ViewModel/AxisEcatVM.cs
no changes added to commit (use "git add" and/or "git commit -a")
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout -b feature/cts
Switched to a new branch 'feature/cts'
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git commit -m "下面给出对 AxisEcatVM 的修复补丁(完整文件),目标:统一管理 CancellationTokenSource、保证 Stop/急停能可靠生效、避免乱造 CTS 导致的竞态和资源泄露。"
[feature/cts 85db0a9] 下面给出对 AxisEcatVM 的修复补丁(完整文件),目标:统一管理 CancellationTokenSource、保证 Stop/急停能可靠生效、避免乱造 CTS 导致的竞 态和资源泄露。
1 file changed, 104 insertions(+), 13 deletions(-)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git commit -m "下面给出对 AxisEcatVM 的修复补丁(完整文件),目标:统一管理 CancellationTokenSource、保证 Stop/急停能可靠生效、避免乱造 CTS 导致的竞态和资源泄露。"
[feature/cts f292c9f] 下面给出对 AxisEcatVM 的修复补丁(完整文件),目标:统一管理 CancellationTokenSource、保证 Stop/急停能可靠生效、避免乱造 CTS 导致的竞 态和资源泄露。
1 file changed, 27 insertions(+), 5 deletions(-)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git commit -m "下面给出对 AxisEcatVM 的修复补丁(完整文件),目标:统一管理 CancellationTokenSource、保证 Stop/急停能可靠生效、避免乱造 CTS 导致的竞态和资源泄露。"
On branch feature/cts
nothing to commit, working tree clean
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/cts)
$ git checkout master
Switched to branch 'master'
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge feature/cts
Updating dacdac2..f292c9f
Fast-forward
WpfApp6/ViewModel/AxisEcatVM.cs | 149 +++++++++++++++++++++++++++++++++++-----
1 file changed, 131 insertions(+), 18 deletions(-)
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "MainViewModel"||true
WpfApp6/MainWindow.xaml.cs:28: // private readonly MainViewModel _viewModel;
WpfApp6/ViewModel/MainViewModel.cs:13: public class MainViewModel:ViewModelBase
WpfApp6/ViewModel/MainViewModel.cs:18: public MainViewModel(IXYScanAxis scanAxis, IEtherCATMaster ecatMaster, ILogService logService)
WpfApp6/WpfApp6.csproj:105: <Compile Include="ViewModel\MainViewModel.cs" />
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout -b archive-mainviewmodel
Switched to a new branch 'archive-mainviewmodel'
11400@ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git mv ViewModel/MainViewModel.cs archive/legacy/MainViewModel.cs
fatal: bad source, source=ViewModel/MainViewModel.cs, destination=archive/legacy/MainViewModel.cs
11400@ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git commit -m "移动MainViewModel到Legacy 感觉没用"
[archive-mainviewmodel 199e3df] 移动MainViewModel到Legacy 感觉没用
1 file changed, 1 deletion(-)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git mv WpfApp6/ViewModel/MainViewModel.cs Archive/legacy/MainViewModel.cs
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git commit -m "移动MainViewModel到Legacy 感觉没用"
[archive-mainviewmodel 611d6f9] 移动MainViewModel到Legacy 感觉没用
1 file changed, 0 insertions(+), 0 deletions(-)
rename {WpfApp6/ViewModel => Archive/legacy}/MainViewModel.cs (100%)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (archive-mainviewmodel)
$ git checkout master
Switched to branch 'master'
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge archive-mainviewmodel
Updating f292c9f..611d6f9
Fast-forward
{WpfApp6/ViewModel => Archive/legacy}/MainViewModel.cs | 0
WpfApp6/WpfApp6.csproj | 1 -
2 files changed, 1 deletion(-)
rename {WpfApp6/ViewModel => Archive/legacy}/MainViewModel.cs (100%)
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout -b feature/bindingbug
Switched to a new branch 'feature/bindingbug'
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bindingbug)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bindingbug)
$ git commit -m "修复绑定问题"
[feature/bindingbug 06e017d] 修复绑定问题
3 files changed, 18 insertions(+), 6 deletions(-)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bindingbug)
$ git add .
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bindingbug)
$ git commit -m "修复绑定问题"
On branch feature/bindingbug
nothing to commit, working tree clean
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bindingbug)
$ git checkout master
Switched to branch 'master'
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge feature/bingingbug
merge: feature/bingingbug - not something we can merge
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git merge feature/bindingbug
Updating 611d6f9..06e017d
Fast-forward
WpfApp6/MainWindow.xaml | 4 ++--
WpfApp6/ViewModel/MainShellVM.cs | 19 ++++++++++++++++---
WpfApp6/WpfApp6.csproj | 1 -
3 files changed, 18 insertions(+), 6 deletions(-)
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout -b feature/bug
Switched to a new branch 'feature/bug'
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "出现了一些问题:textbox中输入数字 自动刷新轴位置 但是图像位置没刷新等"
[feature/bug 708bd61] 出现了一些问题:textbox中输入数字 自动刷新轴位置 但是图 像位置没刷新等
4 files changed, 74 insertions(+), 31 deletions(-)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "出现了一些问题:textbox中输入数字 自动刷新轴位置 但是图像位置没刷新等"
[feature/bug c557a9e] 出现了一些问题:textbox中输入数字 自动刷新轴位置 但是图 像位置没刷新等
2 files changed, 6 insertions(+), 4 deletions(-)
11400@▒▒ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
WpfApp6/MainWindow.xaml
Please commit your changes or stash them before you switch branches.
Aborting
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git add .
11400@ MINGW64 /d/vsprogram/WpfApp6 (feature/bug)
$ git commit -m "出现了一些问题:textbox中输入数字 自动刷新轴位置 但是图像位置没刷新等"
[feature/bug 712ff36] 出现了一些问题:textbox中输入数字 自动刷新轴位置 但是图 像位置没刷新等
1 file changed, 3 insertions(+), 3 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 06e017d..712ff36
Fast-forward
WpfApp6/MainWindow.xaml | 9 +++---
WpfApp6/Messages/AppMessages.cs | 25 ++++++++++-------
WpfApp6/ViewModel/AxisEcatVM.cs | 59 +++++++++++++++++++++++++++++-----------
WpfApp6/ViewModel/MainShellVM.cs | 20 +++++++++++---
4 files changed, 79 insertions(+), 34 deletions(-)
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "new CancellationTokenSource" -- "*.cs" || true
Archive/Legacy/AxisControlViewModel.cs:64: _cts = new CancellationTokenSource();
WpfApp6/ViewModel/AxisEcatVM.cs:94: _cts = new CancellationTokenSource();
WpfApp6/ViewModel/AxisEcatVM.cs:137: //_cts = new CancellationTokenSource();
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n -e ".Wait(" -- ".cs" || true git grep -n -e ".Result" -- ".cs" || true
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "Dispatcher.Invoke(" -- ".cs" || true git grep -n "Dispatcher.InvokeAsync(" -- ".cs" || true
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git grep -n "WeakReferenceMessenger.Default.Register<" -- ".cs" || true git grep -n "UnregisterAll(this)" -- ".cs" || true
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout -b feature/bug
fatal: a branch named 'feature/bug' already exists
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$ git checkout feature/bug
M WpfApp6/MainWindow.xaml.cs
M WpfApp6/Messages/AppMessages.cs
M WpfApp6/Services/LogService.cs
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 "继续修复:^[[200~立即修复:统一 WpfApp6\Messages\AppMessages.cs 为不可变 DTO(Start/Stop/AxisPositionReady/UpdateTarget/Log),全项目搜索并替换 Register/Send 以使用载荷~ 把输入框改为 UpdateSourceTrigger=LostFocus 或显式 Apply 按钮;统一 DataContext 策略(例如 DataContext = this 修复:统一模式:Start 创建 CTS、将 Task 保存在 _runningScanTask;Stop 调用 _cts.Cancel() 并等 待短超时后再强制停机;最后在 finally 中 Dispose CTS。AxisEcatVM 已按该方向重构(继续沿用) 把 Dispatcher.Invoke 改为 Dispatcher.InvokeAsync;在 fire-and-forget 处追加 ContinueWith 记录异常或 await。 确保 AxisEcatVM 在到位时发送 AxisPositionReadyMessage(PosX,PosY)(带坐标),并在 XrayImageVM 使用该坐标触发采集。对运行中目标更新,用 force=true 重启扫描以保证最终到位会再次触发消息。
"
[feature/bug 9ac80b7] 继续修复:立即修复:统一 WpfApp6\Messages\AppMessages.cs 为不可变 DTO(Start/Stop/AxisPositionReady/UpdateTarget/Log),全项目搜索并替换 Register/Send 以使用载荷~ 把输入框改为 UpdateSourceTrigger=LostFocus 或显式 Apply 按钮;统一 DataContext 策略(例如 DataContext = this 修复:统一模式:Start 创建 CTS、将 Task 保存在 _runningScanTask;Stop 调用 _cts.Cancel() 并等待 短超时后再强制停机;最后在 finally 中 Dispose CTS。AxisEcatVM 已按该方向重构(继续沿用) 把 Dispatcher.Invoke 改为 Dispatcher.InvokeAsync;在 fire-and-forget 处追加 ContinueWith 记录异常或 await。 确保 AxisEcatVM 在到位时发送 AxisPositionReadyMessage(PosX,PosY)(带坐标),并在 XrayImageVM 使用该坐标触发采集。对运行中目标更新,用 force=true 重启扫描以保证最终到位会再次触发消息。
6 files changed, 141 insertions(+), 54 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 712ff36..9ac80b7
Fast-forward
WpfApp6/MainWindow.xaml.cs | 13 ++++++-
WpfApp6/Messages/AppMessages.cs | 2 +-
WpfApp6/Services/LogService.cs | 38 ++++++++++++++++----
WpfApp6/ViewModel/AxisEcatVM.cs | 78 ++++++++++++++++++++++++++--------------
WpfApp6/ViewModel/MainShellVM.cs | 20 +++++++----
WpfApp6/ViewModel/XrayImageVM.cs | 44 ++++++++++++++++-------
6 files changed, 141 insertions(+), 54 deletions(-)
11400@ MINGW64 /d/vsprogram/WpfApp6 (master)
$