|
|
@@ -130,7 +130,30 @@ namespace TeamAAS_VP.ViewModels.DebugMod
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Nested ChannelItem class used as item VM
|
|
|
+ // 计划(伪代码,详细步骤):
|
|
|
+ // 1. 在 ChannelItem 内增加用于去抖和写入序列化的字段:
|
|
|
+ // - 一个对象锁 _writeLock 用于保护对共享字段的访问
|
|
|
+ // - 一个可空整型 _latestQueuedBrightness 存储最新的待写入值
|
|
|
+ // - 一个布尔 _isWriting 标识是否正在执行写入操作
|
|
|
+ // - 一个 CancellationTokenSource _debounceCts 用于取消上一次的去抖任务
|
|
|
+ // 2. 在滑动条变化时(ExecuteBrightnessChangedCommand):
|
|
|
+ // - 解析参数得到新的亮度值
|
|
|
+ // - 将新的亮度写入 _latestQueuedBrightness(覆盖之前未写入的值)
|
|
|
+ // - 取消并替换之前的去抖 token,然后启动一个延迟任务(例如 200ms)
|
|
|
+ // 延迟任务被触发后会调用 ProcessWritesAsync 来执行实际写入
|
|
|
+ // 3. 在 ProcessWritesAsync 中:
|
|
|
+ // - 使用锁检查并设置 _isWriting,保证同一时刻只有一个写入循环在跑
|
|
|
+ // - 进入循环:从 _latestQueuedBrightness 读取值并清空该字段,调用写入接口写入该值
|
|
|
+ // - 写入完成后再次检查 _latestQueuedBrightness 是否已有新值;如果有则继续写入最新值,否则退出并将 _isWriting 置 false
|
|
|
+ // 4. 这样可以:
|
|
|
+ // - 去抖:避免用户快速滑动时频繁立刻触发写入
|
|
|
+ // - 合并中间值:只写入最新的有效值
|
|
|
+ // - 串行化写入:避免并发写入接口导致冲突或重复
|
|
|
+ //
|
|
|
+ // 实现注意:
|
|
|
+ // - 不假定底层 SetGlobalChannelBrightnessAsync 支持取消 token(因此不尝试中止正在进行的写入)
|
|
|
+ // - 去抖超时选择 150-300ms 可以根据 UX 需求调整
|
|
|
+
|
|
|
public class ChannelItem : BindableBase
|
|
|
{
|
|
|
private readonly int _globalId;
|
|
|
@@ -161,6 +184,12 @@ namespace TeamAAS_VP.ViewModels.DebugMod
|
|
|
public DelegateCommand<object> BrightnessChangedCommand =>
|
|
|
_BrightnessChangedCommand ?? (_BrightnessChangedCommand = new DelegateCommand<object>(ExecuteBrightnessChangedCommand));
|
|
|
|
|
|
+ // 去抖与写入控制字段
|
|
|
+ private readonly object _writeLock = new object();
|
|
|
+ private int? _latestQueuedBrightness = null;
|
|
|
+ private bool _isWriting = false;
|
|
|
+ private System.Threading.CancellationTokenSource _debounceCts;
|
|
|
+
|
|
|
public ChannelItem(int globalId, ILightChannel channel, ILightManagerService lightManagerService, IConfigService configService, IEventAggregator eventAggregator)
|
|
|
{
|
|
|
_globalId = globalId;
|
|
|
@@ -195,30 +224,116 @@ namespace TeamAAS_VP.ViewModels.DebugMod
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 当滑块变化时调用:采用去抖 + 只写最新值的策略
|
|
|
async void ExecuteBrightnessChangedCommand(object obj)
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
-
|
|
|
var parameter = obj as Tuple<object, double, double>;
|
|
|
+ if (parameter == null) return;
|
|
|
+
|
|
|
+ int newValue = (int)parameter.Item2;
|
|
|
+ // 更新 PendingBrightness 可即时反映到 UI(根据需要)
|
|
|
+ PendingBrightness = newValue;
|
|
|
|
|
|
- var success = await _lightManagerService.SetGlobalChannelBrightnessAsync(_globalId, (int)parameter.Item2);
|
|
|
- //if (success)
|
|
|
- //{
|
|
|
- // CurrentBrightness = PendingBrightness;
|
|
|
- // IsOn = PendingBrightness > 0;
|
|
|
- // // also save as default in config
|
|
|
- // _configService.UpdateChannelDefaultBrightness(_globalId, PendingBrightness);
|
|
|
- // _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"保存完成: {Name} -> {PendingBrightness}", Duration = 1 });
|
|
|
- //}
|
|
|
- //else
|
|
|
- //{
|
|
|
- // _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"保存失败: {Name}", Duration = 1 });
|
|
|
- //}
|
|
|
+ QueueBrightnessChange(newValue);
|
|
|
}
|
|
|
catch (Exception)
|
|
|
{
|
|
|
- //_eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"保存异常: {ex.Message}", Duration = 1 });
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将新的亮度值入队(覆盖之前未写入的值),并启动去抖延迟任务
|
|
|
+ private void QueueBrightnessChange(int brightness)
|
|
|
+ {
|
|
|
+ lock (_writeLock)
|
|
|
+ {
|
|
|
+ _latestQueuedBrightness = brightness;
|
|
|
+ // 取消之前的去抖任务
|
|
|
+ try
|
|
|
+ {
|
|
|
+ _debounceCts?.Cancel();
|
|
|
+ _debounceCts?.Dispose();
|
|
|
+ }
|
|
|
+ catch { }
|
|
|
+ _debounceCts = new System.Threading.CancellationTokenSource();
|
|
|
+ var token = _debounceCts.Token;
|
|
|
+
|
|
|
+ // 启动去抖后处理(不等待)
|
|
|
+ System.Threading.Tasks.Task.Run(async () =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // 去抖延迟,可调整为 150-300ms
|
|
|
+ await System.Threading.Tasks.Task.Delay(200, token);
|
|
|
+ await ProcessWritesAsync();
|
|
|
+ }
|
|
|
+ catch (System.OperationCanceledException)
|
|
|
+ {
|
|
|
+ // 被取消,忽略
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // 忽略其它异常防止未观测异常
|
|
|
+ }
|
|
|
+ }, System.Threading.CancellationToken.None);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 串行化写入循环:写入当前最新值,若在写入过程中又有新值则继续写入,直到没有新值
|
|
|
+ private async System.Threading.Tasks.Task ProcessWritesAsync()
|
|
|
+ {
|
|
|
+ // 快速检查与设置写入标志,保证只有一个写入循环执行
|
|
|
+ lock (_writeLock)
|
|
|
+ {
|
|
|
+ if (_isWriting) return;
|
|
|
+ _isWriting = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ int? toWrite = null;
|
|
|
+ lock (_writeLock)
|
|
|
+ {
|
|
|
+ if (_latestQueuedBrightness.HasValue)
|
|
|
+ {
|
|
|
+ toWrite = _latestQueuedBrightness.Value;
|
|
|
+ _latestQueuedBrightness = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!toWrite.HasValue)
|
|
|
+ {
|
|
|
+ // 没有需要写入的值,退出循环
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // 实际写入(底层方法可能不支持取消)
|
|
|
+ var success = await _lightManagerService.SetGlobalChannelBrightnessAsync(_globalId, toWrite.Value);
|
|
|
+ if (success)
|
|
|
+ {
|
|
|
+ CurrentBrightness = toWrite.Value;
|
|
|
+ PendingBrightness = toWrite.Value;
|
|
|
+ IsOn = toWrite.Value > 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // 忽略写入异常,继续检查是否有新值需要写入
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ lock (_writeLock)
|
|
|
+ {
|
|
|
+ _isWriting = false;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|