在同一个事件处理程序中更新两个视图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在我的视图之后正确更新。
问题的原因是您将计时器间隔指定为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的倍数。
希望对你有帮助。