c# – ViewModel中计时器的更好解决方案?
我在ViewModel中有一个DispatcherTimer用于图形组件,定期更新它(滚动它).
最近我发现这是一个巨大的资源泄漏,因为每次导航到图形视图时都会重新创建ViewModel,并且DispatcherTimer阻止GC破坏我的ViewModel,因为Tick-Event对它有强烈的引用. 我在DispatcherTimer周围使用Wrapper解决了这个问题,它使用Codeproject / Daniel Grunwald中的FastSmartWeakEvent来避免强烈引用VM并在没有更多侦听器时自行销毁: public class WeakDispatcherTimer { /// <summary> /// the actual timer /// </summary> private DispatcherTimer _timer; public WeakDispatcherTimer(TimeSpan interval,DispatcherPriority priority,EventHandler callback,Dispatcher dispatcher) { Tick += callback; _timer = new DispatcherTimer(interval,priority,Timer_Elapsed,dispatcher); } public void Start() { _timer.Start(); } private void Timer_Elapsed(object sender,EventArgs e) { _tickEvent.Raise(sender,e); if (_tickEvent.EventListenerCount == 0) // all listeners have been garbage collected { // kill the timer once the last listener is gone _timer.Stop(); // this un-registers the timer from the dispatcher _timer.Tick -= Timer_Elapsed; // this should make it possible to garbage-collect this wrapper } } public event EventHandler Tick { add { _tickEvent.Add(value); } remove { _tickEvent.Remove(value); } } FastSmartWeakEvent<EventHandler> _tickEvent = new FastSmartWeakEvent<EventHandler>(); } 这就是我使用它的方式.没有“弱”之前,情况完全相同: internal class MyViewModel : ViewModelBase { public MyViewModel() { if (!IsInDesignMode) { WeakDispatcherTimer repaintTimer = new WeakDispatcherTimer(TimeSpan.FromMilliseconds(300),DispatcherPriority.Render,RepaintTimer_Elapsed,Application.Current.Dispatcher); repaintTimer.Start(); } } private void RepaintTimer_Elapsed(object sender,EventArgs e) { ... } } 它似乎运作良好,但这真的是最好/最简单的解决方案还是我错过了什么? 我在谷歌上找不到任何东西,不能相信我是唯一一个在ViewModel中使用计时器来更新某些东西并且资源泄漏的人…这感觉不对! UPDATE 由于图形组件(SciChart)提供了一种附加修饰符(行为)的方法,我写了一个SciChartRollingModifier,这基本上是AlexSeleznyov在他的回答中提出的.使用行为也是可能的,但这更简单! 如果其他人需要滚动的SciChart LineGraph,这是如何做到的: public class SciChartRollingModifier : ChartModifierBase { DispatcherTimer _renderTimer; private DateTime _oldNewestPoint; public SciChartRollingModifier() { _renderTimer = new DispatcherTimer(RenderInterval,RenderTimer_Elapsed,Application.Current.Dispatcher); } /// <summary> /// Updates the render interval one it's set by the property (e.g. with a binding or in XAML) /// </summary> private static void RenderInterval_PropertyChangedCallback(DependencyObject dependencyObject,DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { SciChartRollingModifier modifier = dependencyObject as SciChartRollingModifier; if (modifier == null) return; modifier._renderTimer.Interval = modifier.RenderInterval; } /// <summary> /// this method actually moves the graph and triggers a repaint by changing the visible range /// </summary> private void RenderTimer_Elapsed(object sender,EventArgs e) { DateRange maxRange = (DateRange)XAxis.GetMaximumRange(); var newestPoint = maxRange.Max; if (newestPoint != _oldNewestPoint) // prevent the graph from repainting if nothing changed XAxis.VisibleRange = new DateRange(newestPoint - TimeSpan,newestPoint); _oldNewestPoint = newestPoint; } #region Dependency Properties public static readonly DependencyProperty TimeSpanProperty = DependencyProperty.Register( "TimeSpan",typeof (TimeSpan),typeof (SciChartRollingModifier),new PropertyMetadata(TimeSpan.FromMinutes(1))); /// <summary> /// This is the timespan the graph always shows in rolling mode. Default is 1min. /// </summary> public TimeSpan TimeSpan { get { return (TimeSpan) GetValue(TimeSpanProperty); } set { SetValue(TimeSpanProperty,value); } } public static readonly DependencyProperty RenderIntervalProperty = DependencyProperty.Register( "RenderInterval",new PropertyMetadata(System.TimeSpan.FromMilliseconds(300),RenderInterval_PropertyChangedCallback)); /// <summary> /// This is the repaint interval. In this interval the graph moves a bit and repaints. Default is 300ms. /// </summary> public TimeSpan RenderInterval { get { return (TimeSpan) GetValue(RenderIntervalProperty); } set { SetValue(RenderIntervalProperty,value); } } #endregion #region Overrides of ChartModifierBase protected override void OnIsEnabledChanged() { base.OnIsEnabledChanged(); // start/stop the timer only of the modifier is already attached if (IsAttached) _renderTimer.IsEnabled = IsEnabled; } #endregion #region Overrides of ApiElementBase public override void OnAttached() { base.OnAttached(); if (IsEnabled) _renderTimer.Start(); } public override void OnDetached() { base.OnDetached(); _renderTimer.Stop(); } #endregion } 解决方法
我可能没有得到你正在追求的东西,但对我来说,看起来你正在为ViewModel提供比它能处理的功能更多的功能.在视图模型中使用计时器会使单元测试更加困难.
我将这些步骤提取到一个单独的组件,该组件将通知ViewModel计时器间隔已过去.而且,如果实现为Interactivity Behavior,这个单独的组件将确切地知道何时创建/销毁View(通过OnAttached / OnDetached方法),并且反过来可以启动/停止计时器. 这里的另一个好处是您可以轻松地对ViewModel进行单元测试. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |