Reputation: 297
I have written my own timer class, a wrapper for Windows Multimedia Timers.
I modelled my timer class on .NET's System.Timers.Timer
class.
I am struggling to understand why .NET's timer calls BeginInvoke
on the synchronizing object, rather than Invoke
:
if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
{
this.SynchronizingObject.BeginInvoke(intervalElapsed, new object[]{this, elapsedEventArgs};
}
else
{
intervalElapsed(this, elapsedEventArgs);
}
My understanding was that BeginInvoke
should eventually be matched by a call to EndInvoke
. There is no EndInvoke
to be found.
Is there anything "wrong" with the way .NET is doing it? Why might BeginInvoke
be preferable here?
If I use BeginInvoke
in my class too, would that mean my class' timer event could fire before a previous event has completed (re-entrancy issues)? Wouldn't that defeat (some of) the purpose of synchronizing to a synchronizing object?
Upvotes: 1
Views: 548
Reputation: 43384
The Invoke
method blocks the calling thread until the completion of the execution of the supplied delegate, which may take a long amount of time, for various reasons. For example the delegate may contain blocking calls, or the target context may be temporarily blocked, etc. The calling thread in the case of the System.Timers.Timer
class is always a ThreadPool
thread, which is a pool of limited resources. Blocking a ThreadPool
thread is a bad idea, because it can easily lead to the saturation of the pool, causing inefficiency and reduced responsiveness. That's why calling the BeginInvoke
is preferable, because it just schedules the execution of the delegate, and scheduling is normally a pretty fast operation. Calling the EndInvoke
is not required.
The System.Timers.Timer.Elapsed
event is re-entrant by design, and it would still be re-entrant if it called the Invoke
instead of the BeginInvoke
. That's because the event is triggered on a pool of threads. For comparison the System.Windows.Forms.Timer.Tick
event is not re-entrant, because it is triggered always on the same thread, the UI thread. The re-entrant nature of the System.Timers.Timer.Elapsed
event is an argument for not using the System.Timers.Timer
class, and using instead an asynchronous loop. You can look at this question for examples: Run async method regularly with specified interval. Other arguments for not using this class are that the event handler swallows exceptions, and that the class is not thread-safe. Thread-safe classes do not expose events in general, because an event is an inherently not-thread-safe mechanism.
Upvotes: 2
Reputation: 141
The Invoke will block current thread and main thread.
And BeginInvoke only block main thread.
you can try in wpf
MainWindow.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);
}
}
}
Upvotes: 1