在同一个事件处理程序中更新两个视图UI元素会产生不稳定的结果

本文关键字:元素 UI 视图 结果 不稳定 两个 程序 事件处理 同一个 更新 | 更新日期: 2023-09-27 18:09:27

我有一个小程序,它比一个运行和更新视图的秒表大不了多少。每隔8秒,秒表也会在该视图上增加一个计数器。所以在0、8、16、24秒等等…计数器为1、2、3、4等

在我的视图的XAML中,我有几个项目,其中一个是用于放置我的秒表的TextBlock,另一个是用于显示"8秒过去了"的时间的另一个TextBlock。

<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left" >
    <Label Content="Run Time:" FontSize="16" FontWeight="Bold" Margin="10,0,0,0"/>
    <TextBlock Name="ClockTextBlock" Text="00:00:00:00" FontSize="16" Foreground="Red" Margin="5" FontWeight="Bold"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
    <Label Content="Sample Count:" FontSize="16" FontWeight="Bold" Margin="10,0,0,0"/>
    <TextBlock Text="0" Name="SampleCountDigit" Foreground="Red" FontSize="16" FontWeight="Bold" Margin="5"/>
</StackPanel> 

这个xaml文件后面的代码包含设置调度计时器的代码,该计时器将创建我的秒表。

public partial class StopWatchView: UserControl
{
    private DispatcherTimer dt = new DispatcherTimer();
    private Stopwatch stopWatch = new Stopwatch();
    private string _currentTime = string.Empty;
    private int _sampleCount = 0;
    public StopWatchView()
    {
        InitializeComponent();
        dt.Tick += new EventHandler(dt_Tick);
        dt.Interval = new TimeSpan(0, 0, 0, 0, 1);
    }
    private void dt_Tick(object sender, EventArgs e)
    {
        if (stopWatch.IsRunning)
        {
            TimeSpan ts = stopWatch.Elapsed;
            _currentTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds,
                                        ts.Milliseconds/10);
            ClockTextBlock.Text = _currentTime;
            if (ts.Seconds%8 == 0)
            {
                _sampleCount++;
                SampleCountDigit.Text = _sampleCount.ToString();
            }
        }
    }
    private void StartButton_Click(object sender, RoutedEventArgs e)
    {
        ClockTextBlock.Foreground = Brushes.Green;
        stopWatch.Start();
        dt.Start();
    }

代码可以正确地更新我的秒表,使其看起来和行为都像一个秒表。每次调用dt_tick()并且if(stopWatch.IsRunning)求值为true时,视图都会顺利更新。我遇到的问题是当if(ts.Seconds%8 == 0)为真时_sampleCount增加。我不知道这是否是一个竞争条件(因为我还在学习线程),但TextBox="SampleCountDigit"更新得很快,而且不规律,而不是每隔8秒就更新一次。老实说,从我对线程的了解,我不明白为什么会发生这种情况,或者当这两个成员变量(_sampleCount和_currentTime)在dt_Tick()事件处理程序代码中更新时,为什么会出现竞争条件。

为什么会发生这种情况,我能做些什么来更新我的SampleCountDigit(视图),以便每8秒应该?是否应该围绕该元素的更新编写一个新线程?

EDIT为了更好地了解问题的行为,当我在SampleCountDigit.Text = _sampleCount.ToString();上设置断点时,代码在8,16,24秒等处完美停止……和SampleDigitCounter在我的视图之后正确更新。

在同一个事件处理程序中更新两个视图UI元素会产生不稳定的结果

问题的原因是您将计时器间隔指定为1毫秒。因此,dt_tick()每秒最多可以被调用1000次,在此期间ts.Second的值不会改变。这将导致_sampleCount每8秒增加1000。

实际上,dt_tick将以较低的速率被调用,这取决于DispatcherTimer的能力和机器的速度。

你可以这样修改你的代码:

int _lastSeconds = -1;
private void dt_Tick(object sender, EventArgs e)
{
    if (stopWatch.IsRunning)
    {
        ...
        if (ts.Seconds != _lastSeconds && ts.Seconds%8 == 0)
        {
            _lastSeconds = ts.Seconds;
            _sampleCount++;
            SampleCountDigit.Text = _sampleCount.ToString();
        }
    }
}

您的直觉认为这不是线程问题,因为您在同一个方法中修改了两个变量,因此,在同一个线程中。

你的问题其实很简单。我假定你的dt_tick()方法每秒被调用不止一次。现在,如果你碰巧在同一秒内触发事件两次或三次,并且在特定的一秒内"ts.Seconds"恰好是8的倍数,那么你将增加你的计数器三次。

你在这里遇到的另一个小问题是你使用了"ts.Seconds"而不是"ts.TotalSeconds",我们将在下面的解决方案中看到它的影响。

解决这个问题的最好方法是使用ts.TotalSeconds来获得秒表启动后的绝对秒数,并在每次更新计数器时跟踪该值。然后可以比较上次更新时间和现在更新时间,看看是否已经过去了8秒。使用ts.TotalSeconds而不是ts.Seconds很重要,因为ts.Seconds就是"秒针",只在0的范围内移动。所以如果你使用它,你可能会得到一些奇怪的东西,比如时间倒流等等。

// Outside your dt_tick()
double lastIncrementTime = 0.0d;
// Inside your dt_tick(), just after ClockTextBlock.Text = _currentTime;
if (ts.TotalSeconds - lastIncrementTime >= 8.0d)
{
    // increment counter etc, then...
    lastIncrementTime = ts.TotalSeconds;
}

这样,无论你多频繁地运行代码,你实际上是在检查自上次增加计数器以来是否已经过去了8秒,而不是检查秒针上的当前值是否是8的倍数。

希望对你有帮助。