Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/12.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/mongodb/12.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# ViewModel中计时器的更好解决方案?_C#_Wpf_Mvvm_Scichart - Fatal编程技术网

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

C# ViewModel中计时器的更好解决方案?,c#,wpf,mvvm,scichart,C#,Wpf,Mvvm,Scichart,我在ViewModel中为图形组件提供了一个Dispatcher,用于定期更新(滚动) 最近,我发现这是一个巨大的资源泄漏,因为每次我导航到图形视图时,ViewModel都是新创建的,Dispatchermer阻止GC破坏我的ViewModel,因为Tick事件对它有很强的引用 我用Dispatchermer的包装解决了这个问题,它使用from Codeproject/Daniel Grunwald来避免对VM的强烈引用,并在没有更多侦听器时销毁自己: public class WeakDisp

我在ViewModel中为图形组件提供了一个Dispatcher,用于定期更新(滚动)

最近,我发现这是一个巨大的资源泄漏,因为每次我导航到图形视图时,ViewModel都是新创建的,Dispatchermer阻止GC破坏我的ViewModel,因为Tick事件对它有很强的引用

我用Dispatchermer的包装解决了这个问题,它使用from Codeproject/Daniel Grunwald来避免对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>(); 
}
这似乎很有效,但这真的是最好/最简单的解决方案还是我遗漏了什么

我在谷歌上完全找不到任何东西,不敢相信我是唯一一个在ViewModel中使用计时器来更新内容并导致资源泄漏的人。。。那感觉不对

更新

由于图形组件(SciChart)提供了一种附加修饰符(行为)的方法,因此我编写了一个SciChartRollingModifier,这基本上就是Alexeleznyov在回答中所建议的。如果有一个行为,这也是可能的,但这更简单

如果其他任何人需要滚动SciChart线形图,请按照以下方法操作:

public class SciChartRollingModifier : ChartModifierBase
{
    DispatcherTimer _renderTimer;

    private DateTime _oldNewestPoint;



    public SciChartRollingModifier()
    {
        _renderTimer = new DispatcherTimer(RenderInterval, DispatcherPriority.Render, 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", typeof (TimeSpan), typeof (SciChartRollingModifier), 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
}
公共类SciChartRollingModifier:ChartModifierBase
{
调度员(renderTimer),;
私有日期时间_oldNewestPoint;
公共SciChartRollingModifier()
{
_renderTimer=新的调度程序(RenderInterval、DispatcherPriority.Render、renderTimer\u、Application.Current.Dispatcher);
}
/// 
///更新由属性设置的渲染间隔(例如,使用绑定或XAML)
/// 
私有静态void RenderInterval_属性ChangedCallback(DependencyObject DependencyObject,DependencyPropertyChangedEventArgs DependencyPropertyChangedEventArgs)
{
SciChartRollingModifier=dependencyObject作为SciChartRollingModifier;
if(修饰符==null)
返回;
修饰符。_renderTimer.Interval=modifier.RenderInterval;
}
/// 
///此方法实际上会移动图形,并通过更改可见范围触发重新绘制
/// 
私有void RenderTimer_已过期(对象发送方,事件参数e)
{
DateRange maxRange=(DateRange)XAxis.GetMaximumRange();
var newestPoint=maxRange.Max;
if(newestPoint!=\u oldNewestPoint)//如果没有更改,则阻止图形重新绘制
XAxis.VisibleRange=新日期范围(newestPoint-TimeSpan,newestPoint);
_oldNewestPoint=newestPoint;
}
#区域依赖属性
公共静态只读DependencyProperty TimeSpanProperty=DependencyProperty.Register(
“TimeSpan”、typeof(TimeSpan)、typeof(SciChartRollingModifier)、新属性元数据(TimeSpan.FromMinutes(1));
/// 
///这是图表始终以滚动模式显示的时间跨度。默认值为1分钟。
/// 
公共时间跨度时间跨度
{
获取{return(TimeSpan)GetValue(TimeSpan属性);}
set{SetValue(timespan属性,值);}
}
公共静态只读DependencyProperty RenderInervalProperty=DependencyProperty.Register(
“RenderInterval”、typeof(TimeSpan)、typeof(SciChartRollingModifier)、新的属性元数据(System.TimeSpan.From毫秒(300),RenderInterval_PropertyChangedCallback));
/// 
///这是重绘间隔。在此间隔内,图形移动一点并重新绘制。默认值为300ms。
/// 
公共时间跨度RenderInterval
{
获取{return(TimeSpan)GetValue(RenderIntervalProperty);}
设置{SetValue(RenderIntervalProperty,value);}
}
#端区
#ChartModifierBase的区域覆盖
受保护的覆盖无效OnIsEnabledChanged()
{
base.OnIsEnabledChanged();
//启动/停止仅已连接修改器的计时器
如果(已附加)
_renderTimer.IsEnabled=IsEnabled;
}
#端区
#APElementBase的区域覆盖
公共覆盖无效附加()
{
base.onatached();
如果(已启用)
_Start();
}
public override void ondattached()
{
base.OnDetached();
_renderTimer.Stop();
}
#端区
}

您可以将视图的
关闭
事件绑定到ViewModel中的
命令
,在
调度计时器上调用
停止()
方法。这将允许对计时器和ViewModel进行CG:ed

考虑视图

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <command:EventToCommand Command="{Binding CloseCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

另一种解决方案可能是将计时器设置为静态,或者在ViewModelLocator或类似的位置保持对VM的静态引用。

我可能没有完全了解您的目标,但在我看来,您在ViewModel中添加的功能似乎超出了它的处理能力。在视图模型中使用计时器会使单元测试更加困难

我将这些步骤提取到一个单独的组件中,该组件将通知ViewModel计时器间隔已过。而且,如果作为一个组件实现,这个单独的组件将准确地知道视图的创建/销毁时间(通过OnAttached/OnAttached方法),进而可以启动/停止计时器


这里的另一个好处是,您可以轻松地对ViewModel进行单元测试。

图形是否不支持侦听
PropertyChanged
事件来更新自身?为什么不直接使用WeakReference?您好,我有类似的问题,但我在使用dispatcher和交互行为方面是新手,所以我想知道我怎样才能在xaml中使用它?我想这是我要走的路。我甚至可以将整个工作部分转移到行为中,这样它就完全可重用了!我将在上面发布我的代码,这不是一个交互性行为,因为graph组件有一些类似的东西,但它几乎是相同的(使用OnAttached/OnAttached)。这个解决方案效果很好,但我更喜欢@Alexeleznyov解决方案。。抱歉:)这正是我们在S中使用的技术
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <command:EventToCommand Command="{Binding CloseCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>
public class MyViewModel : ViewModelBase
{
    public MyViewModel()
    {
        DispatcherTimer timer = new DispatcherTimer(
            TimeSpan.FromSeconds(1),
            DispatcherPriority.Render,
            (sender, args) => Console.WriteLine(@"tick"),
            Application.Current.Dispatcher);
        timer.Start();

        CloseCommand = new RelayCommand(() => timer.Stop());
    }

    public ICommand CloseCommand { get; set; }
}