微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

ISynchronizeInvoke Invoke 与 BeginInvoke

如何解决ISynchronizeInvoke Invoke 与 BeginInvoke

我已经编写了自己的计时器类,它是 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

  1. .NET 的做法有什么“错误”吗?为什么 BeginInvoke 在这里更可取?

  2. 如果我也在我的班级中使用 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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。