ISynchronizeInvoke 调用与 BeginInvoke

ISynchronizeInvoke Invoke vs BeginInvoke

我已经编写了自己的计时器 class,Windows Multimedia Timers 的包装器。

我在 .NET 的 System.Timers.Timer class.

上建模了我的计时器 class

我很难理解为什么 .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

  1. .NET 的处理方式有什么“错误”吗?为什么 BeginInvoke 在这里更可取?

  2. 如果我也在我的 class 中使用 BeginInvoke,这是否意味着我的 class' 计时器事件可能会在前一个事件完成之前触发(重新入学问题)?这不会破坏(部分)同步到同步对象的目的吗?

调用会阻塞当前线程和主线程。

而 BeginInvoke 只阻塞主线程。

你可以在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);
        }
    }
}

不需要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

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: . 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.