gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 13:55:27 24 4
gpt4 key购买 nike

我在 ViewModel 中有一个用于图形组件的 DispatcherTimer,用于定期更新它(滚动它)。

最近我发现这是一个巨大的资源泄漏,因为每次我导航到图形 View 时都会新创建 ViewModel,并且 DispatcherTimer 会阻止 GC 破坏我的 ViewModel,因为 Tick-Event 对它有很强的引用。

我用一个围绕 DispatcherTimer 的包装器解决了这个问题,它使用 FastSmartWeakEvent来自 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>();
}

我就是这样用的。这与之前没有“弱”的完全一样:

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 中使用计时器来更新某些东西并导致资源泄漏的人......这感觉不对!

更新

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

如果其他人需要滚动 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
}

最佳答案

我可能无法准确理解您的需求,但在我看来,您向 ViewModel 中添加的功能似乎超出了它的处理能力。在 View 模型中使用计时器会使单元测试变得更加困难。

我会将这些步骤提取到一个单独的组件中,该组件会通知 ViewModel 计时器间隔已用完。而且,如果实现为 Interactivity Behavior ,这个单独的组件会确切地知道 View 何时创建/销毁(通过 OnAttached/OnDetached 方法),并且反过来可以启动/停止计时器。

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

关于c# - ViewModel 中计时器的更好解决方案?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34683948/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com