1

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.

  1. Is there anything "wrong" with the way .NET is doing it? Why might BeginInvoke be preferable here?

  2. 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?

Michal Cihelka
  • 187
  • 1
  • 9

2 Answers2

2

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • You have clarified so much, thank you. Are you able to clarify one thing - you mention that "BeginInvoke schedules the execution of the delegate", what mechanism deals with the scheduling? In particular, what happens if multiple BeginInvokes schedule a delegate faster than the delegate method executes its code? Are the calls queued in some way or is *that* where we get the annoying re-entrancy issues? – Michal Cihelka Apr 28 '21 at 06:53
  • 1
    @MichalCihelka the scheduling mechanism is part of the specific `ISynchronizeInvoke` implementation. In practice 99% of the time it schedules work on the UI thread of a GUI-enabled application, which maintains an internal queue of scheduled delegates. What happens when this queue is flooded with messages, is discussed in this question: [WinForms message loop not responsive](https://stackoverflow.com/questions/6789105/winforms-message-loop-not-responsive). – Theodor Zoulias Apr 28 '21 at 07:02
  • 1
    Wow. I've learnt more from this one answer than from days of google searching on delegates, events, and multithreading. Can't thank you enough - for your answer and the invaluable links. I see things much clearer now - understanding the underlying message loop architecture of Windows is key. Now, armed with this knowledge, I must decide whether to `Invoke` or `BeginInvoke` in *my* timer. I expect that in practice it won't (or shouldn't) make any real difference. – Michal Cihelka Apr 28 '21 at 07:51
  • If I may expand on my last sentence, "I expect that in practice it won't (or shouldn't) make any real difference". For the benefit of others, the reason why it won't make much difference in my case is because my timer is not intended for doing anything on the main UI thread. But if I were to use my timer to, say, update a control on a form, it seems like `BeginInvoke` is the better/safer way to go. – Michal Cihelka Apr 28 '21 at 12:13
1

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);
        }
    }
}
Allen Hu
  • 141
  • 6
  • 1
    I really appreciate your efforts, thank you :-) What I'm really looking for is more clarity around the *implications* of choosing `BeginInvoke` over `Invoke` when raising events, and the reasons MS might have had for choosing one over the other in their own timer's callback code which raises the Elapsed event. – Michal Cihelka Apr 28 '21 at 07:07