加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – ViewModel中计时器的更好解决方案?

发布时间:2020-12-15 23:30:11 所属栏目:百科 来源:网络整理
导读:我在ViewModel中有一个DispatcherTimer用于图形组件,定期更新它(滚动它). 最近我发现这是一个巨大的资源泄漏,因为每次导航到图形视图时都会重新创建ViewModel,并且DispatcherTimer阻止GC破坏我的ViewModel,因为Tick-Event对它有强烈的引用. 我在DispatcherTi
我在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进行单元测试.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读