0

我有一个小程序,它只不过是一个运行和更新视图的秒表。每 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)评估为真时,视图都会顺利更新。我遇到问题的地方是当为真时_sampleCount增加。if(ts.Seconds%8 == 0)我不知道它是否是一种竞争条件(因为我仍在学习线程),但TextBox="SampleCountDigit"更新迅速且不规律,而不是应有的 8 秒间隔。老实说,根据我对线程的了解,我不明白为什么会发生这种情况,或者当这两个成员变量(_sampleCount 和 _currentTime)都在 dt_Tick() 事件处理程序代码中更新时,为什么会成为竞争条件。

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

编辑为了更好地了解问题的行为,当我在代码上设置断点时,SampleCountDigit.Text = _sampleCount.ToString();在 8、16、24 秒等处完全停止......并且我视图上的 SampleDigitCounter 此后会正确更新。

4

2 回答 2

2

问题是因为您已将计时器间隔指定为 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();
        }
    }
}
于 2013-04-16T20:53:33.887 回答
1

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

您的问题实际上很简单。我认为您的 dt_tick() 方法每秒被调用一次以上。现在,如果您碰巧在同一秒内触发了该事件两次或三次,并且该特定秒“ts.Seconds”恰好是 8 的倍数,您将增加您的计数器三次。

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

解决此问题的最佳方法是使用 ts.TotalSeconds 获取自秒表启动以来的绝对秒数,并在每次更新计数器时跟踪该值。然后你可以比较上次更新时间和现在,看看是否已经过了 8 秒。使用 ts.TotalSeconds 而不是 ts.Seconds 很重要,因为 ts.Seconds 只是“秒针”,并且只能在 0..59 范围内移动,因此如果您使用 ts.TotalSeconds,您可能会遇到一些奇怪的事情,例如时间倒退等用它。

// 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 的倍数.

希望有帮助。

于 2013-04-16T20:59:59.623 回答