问题描述
我已经编写了自己的计时器类,它是 Windows Multimedia Timers 的包装器。
我在 .NET 的 System.Timers.Timer
类上建模了我的计时器类。
我很难理解为什么 .NET 的计时器在同步对象上调用 BeginInvoke
,而不是 Invoke
:
if (this.SynchronizingObject != null && this.SynchronizingObject.Invokerequired)
{
this.SynchronizingObject.BeginInvoke(intervalElapsed,new object[]{this,elapsedEventArgs};
}
else
{
intervalElapsed(this,elapsedEventArgs);
}
我的理解是,BeginInvoke
最终应该通过调用 EndInvoke
来匹配。找不到EndInvoke
。
-
如果我也在我的班级中使用
BeginInvoke
,这是否意味着我班级的计时器事件可能会在前一个事件完成之前触发(重入问题)?这不会破坏(部分)同步到同步对象的目的吗?
解决方法
Invoke 会阻塞当前线程和主线程。
而 BeginInvoke 只阻塞主线程。
你可以试试wpf
主窗口.xaml
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<UniformGrid Columns="1">
<TextBlock Text="{Binding TimeString}"/>
<Button Content="Invoke" Click="Invoke_Button_Click"/>
<Button Content="BeginInvoke" Click="BeginInvoke_Button_Click"/>
</UniformGrid>
</Window>
MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApp5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string tmeString;
public string TimeString
{
get { return this.tmeString; }
set
{
this.tmeString = value;
PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(nameof(TimeString)));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Task.Run(() =>
{
while (true)
{
TimeString = $"DateTimeNow : {DateTime.Now}";
Thread.Sleep(1000);
}
});
}
private void BeginInvoke_Button_Click(object sender,RoutedEventArgs e)
{
Dispatcher.BeginInvoke((Action)SomeWork,null);
//break point here
bool buttonClickEventEnd = true;
}
private void Invoke_Button_Click(object sender,RoutedEventArgs e)
{
Dispatcher.Invoke((Action)SomeWork,null);
//break point here
bool buttonClickEventEnd = true;
}
private void SomeWork()
{
Thread.Sleep(3 * 1000);
}
}
}
,
Invoke
方法会阻塞调用线程,直到所提供委托的执行完成为止,这可能需要很长时间,出于各种原因。例如委托可能包含阻塞调用,或者目标上下文可能被临时阻塞等。System.Timers.Timer
类的情况下调用线程始终是一个ThreadPool
线程,这是一个有限的池资源。阻塞一个 ThreadPool
线程是一个坏主意,因为它很容易导致池饱和,导致效率低下和响应能力下降。这就是调用 BeginInvoke
更可取的原因,因为它只是调度委托的执行,并且调度通常是一个非常快的操作。不需要调用 EndInvoke
。
System.Timers.Timer.Elapsed
事件在设计上是可重入的,如果它调用 Invoke
而不是 BeginInvoke
,它仍然是可重入的。那是因为该事件是在线程池上触发的。相比之下,System.Windows.Forms.Timer.Tick
事件不是可重入的,因为它总是在同一个线程(UI 线程)上触发。 System.Timers.Timer.Elapsed
事件的可重入性质是不使用 System.Timers.Timer
类而是使用异步循环的参数。您可以查看此问题的示例:Run async method regularly with specified interval。不使用此类的其他参数是事件处理程序 swallows exceptions,并且该类不是线程安全的。线程安全类一般不公开事件,因为事件是 inherently not-thread-safe mechanism。