初期稳定版本260119
This commit is contained in:
@@ -13,6 +13,7 @@ using System.Windows;
|
||||
using System.Text;
|
||||
using Prism.Events;
|
||||
using FATrace.WPLApp.Events;
|
||||
using System.Threading;
|
||||
|
||||
namespace FATrace.WPLApp.ViewModels
|
||||
{
|
||||
@@ -22,6 +23,9 @@ namespace FATrace.WPLApp.ViewModels
|
||||
private readonly ILogService _log;
|
||||
private readonly DataServices _data;
|
||||
|
||||
private int _dashboardRefreshRequested;
|
||||
private int _dashboardRefreshLoopRunning;
|
||||
|
||||
private DispatcherTimer _logTimer;
|
||||
private bool _initialized;
|
||||
private TextWriter _originalConsoleOut;
|
||||
@@ -37,6 +41,7 @@ namespace FATrace.WPLApp.ViewModels
|
||||
_ea = ea;
|
||||
|
||||
LiveMessages = new ObservableCollection<string>();
|
||||
LineTempCodes = new ObservableCollection<LineTempCode>();
|
||||
|
||||
RefreshCommand = new DelegateCommand(async () => await RefreshStatsAsync());
|
||||
ClearLogsCommand = new DelegateCommand(() => LiveMessages.Clear());
|
||||
@@ -66,7 +71,13 @@ namespace FATrace.WPLApp.ViewModels
|
||||
};
|
||||
_data.LineSglModel.BoxSprayCodeReqHandle += (s, e) =>
|
||||
{
|
||||
AppendLiveMessage("外箱喷码请求: 已向PLC下发喷码数据");
|
||||
var code = _data.BoxSprayCode;
|
||||
if (Application.Current?.Dispatcher?.CheckAccess() == true)
|
||||
LatestBoxSprayCode = code;
|
||||
else
|
||||
Application.Current?.Dispatcher?.BeginInvoke(new Action(() => LatestBoxSprayCode = code));
|
||||
//AppendLiveMessage("外箱喷码请求: 已向PLC下发喷码数据");
|
||||
AppendLiveMessage($"外箱喷码请求: 已向PLC下发喷码数据;{code}");
|
||||
};
|
||||
_data.LineSglModel.BoxScanCodeReqHandle += (s, e) =>
|
||||
{
|
||||
@@ -81,12 +92,89 @@ namespace FATrace.WPLApp.ViewModels
|
||||
// 订阅外部刷新事件(来自 DataServices 的 BoxScanCode 完成后)
|
||||
try
|
||||
{
|
||||
_dashEventToken = _ea.GetEvent<DashboardRefreshEvent>().Subscribe(_ =>
|
||||
{
|
||||
Application.Current?.Dispatcher?.BeginInvoke(new Action(async () => await RefreshStatsAsync()));
|
||||
});
|
||||
EnsureDashboardRefreshSubscription();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.Warn($"DashboardRefreshEvent 订阅失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureDashboardRefreshSubscription()
|
||||
{
|
||||
if (_dashEventToken != null) return;
|
||||
|
||||
// 注意:Prism 默认 keepSubscriberReferenceAlive=false(弱引用)。
|
||||
// 若使用 lambda,目标可能是 closure 对象,存在被 GC 导致订阅失效的风险。
|
||||
// 这里使用具名方法 + keepSubscriberReferenceAlive=true,确保订阅稳定。
|
||||
_dashEventToken = _ea.GetEvent<DashboardRefreshEvent>()
|
||||
.Subscribe(OnDashboardRefreshEvent, ThreadOption.UIThread, true);
|
||||
}
|
||||
|
||||
private void OnDashboardRefreshEvent(bool _)
|
||||
{
|
||||
RequestDashboardRefresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发一次 Dashboard 刷新(合并 + 互斥):短时间多次触发只会串行执行,并在执行结束后最多再补跑一次。
|
||||
/// </summary>
|
||||
private void RequestDashboardRefresh()
|
||||
{
|
||||
// 标记有刷新请求
|
||||
Interlocked.Exchange(ref _dashboardRefreshRequested, 1);
|
||||
|
||||
// 若刷新循环已在跑,则仅标记请求即可
|
||||
if (Interlocked.Exchange(ref _dashboardRefreshLoopRunning, 1) == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dispatcher = Application.Current?.Dispatcher;
|
||||
if (dispatcher == null)
|
||||
{
|
||||
// 极少数场景(无 UI Dispatcher),直接后台跑
|
||||
_ = DashboardRefreshLoopAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await DashboardRefreshLoopAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _dashboardRefreshLoopRunning, 0);
|
||||
|
||||
// 如果在执行过程中又来了刷新请求,则再次启动一次循环(确保不漏刷新)
|
||||
if (Interlocked.Exchange(ref _dashboardRefreshRequested, 0) == 1)
|
||||
{
|
||||
RequestDashboardRefresh();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 UI 线程串行执行刷新,且合并多次触发。
|
||||
/// </summary>
|
||||
private async Task DashboardRefreshLoopAsync()
|
||||
{
|
||||
// 只要存在刷新请求,就执行一次刷新;过程中再来请求,会在下一轮继续跑
|
||||
while (Interlocked.Exchange(ref _dashboardRefreshRequested, 0) == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RefreshStatsAsync();
|
||||
await RefreshLineTempCodesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error($"DashboardRefreshEvent 刷新失败: {ex}");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@@ -108,17 +196,28 @@ namespace FATrace.WPLApp.ViewModels
|
||||
private string _latestBoxScanCode;
|
||||
public string LatestBoxScanCode { get => _latestBoxScanCode; set { _latestBoxScanCode = value; RaisePropertyChanged(); } }
|
||||
|
||||
private string _latestBoxSprayCode;
|
||||
/// <summary>
|
||||
/// 最近一次外箱喷码数据(即下发给 PLC 的源字符串)
|
||||
/// </summary>
|
||||
public string LatestBoxSprayCode { get => _latestBoxSprayCode; set { _latestBoxSprayCode = value; RaisePropertyChanged(); } }
|
||||
|
||||
private bool _plcConnected;
|
||||
public bool PlcConnected { get => _plcConnected; set { _plcConnected = value; RaisePropertyChanged(); } }
|
||||
|
||||
public ObservableCollection<string> LiveMessages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 产线临时条码队列(内包扫码入队,外箱扫码确认后出队)
|
||||
/// </summary>
|
||||
public ObservableCollection<LineTempCode> LineTempCodes { get; }
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
public DelegateCommand RefreshCommand { get; }
|
||||
public DelegateCommand ClearLogsCommand { get; }
|
||||
#endregion
|
||||
|
||||
|
||||
private void AppendLiveMessage(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message)) return;
|
||||
@@ -186,8 +285,63 @@ namespace FATrace.WPLApp.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库刷新产线临时队列(LineTempCode 表)并同步到 UI。
|
||||
/// </summary>
|
||||
private async Task RefreshLineTempCodesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await Task.Run(() =>
|
||||
{
|
||||
return _fsql.Select<LineTempCode>()
|
||||
.OrderBy(a => a.Id)
|
||||
.Limit(200)
|
||||
.ToList();
|
||||
});
|
||||
|
||||
void apply()
|
||||
{
|
||||
LineTempCodes.Clear();
|
||||
foreach (var item in list)
|
||||
{
|
||||
LineTempCodes.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
var dispatcher = Application.Current?.Dispatcher;
|
||||
if (dispatcher == null)
|
||||
{
|
||||
apply();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispatcher.CheckAccess())
|
||||
{
|
||||
apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
await dispatcher.InvokeAsync(apply).Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error($"刷新 LineTempCode 队列失败: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(Prism.Regions.NavigationContext navigationContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureDashboardRefreshSubscription();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.Warn($"DashboardRefreshEvent OnNavigatedTo 订阅失败: {ex.Message}");
|
||||
}
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
_initialized = true;
|
||||
@@ -196,6 +350,8 @@ namespace FATrace.WPLApp.ViewModels
|
||||
// 初始化展示最近一次扫描值
|
||||
LatestWeightScanCode = _data.WeightScanCode;
|
||||
LatestBoxScanCode = _data.BoxScanCode;
|
||||
LatestBoxSprayCode = _data.BoxSprayCode;
|
||||
await RefreshLineTempCodesAsync();
|
||||
TryHookConsole();
|
||||
}
|
||||
}
|
||||
@@ -208,7 +364,18 @@ namespace FATrace.WPLApp.ViewModels
|
||||
_logTimer = null;
|
||||
}
|
||||
UnhookConsole();
|
||||
try { if (_dashEventToken != null) _ea.GetEvent<DashboardRefreshEvent>().Unsubscribe(_dashEventToken); } catch { }
|
||||
try
|
||||
{
|
||||
if (_dashEventToken != null)
|
||||
{
|
||||
_ea.GetEvent<DashboardRefreshEvent>().Unsubscribe(_dashEventToken);
|
||||
_dashEventToken = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.Warn($"DashboardRefreshEvent 退订失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void TryHookConsole()
|
||||
|
||||
Reference in New Issue
Block a user