C# 不一致的调度程序。调用行为

C# 不一致的调度程序。调用行为,c#,wpf,dispatcher,C#,Wpf,Dispatcher,我为一个服务台团队创建了一个墙板应用程序,前端使用WPF,后端使用Cisco手机数据库。该应用程序由两个显示不同信息的屏幕组成,这些屏幕显示在同一屏幕上,并通过System.Timers.Timer 应用程序是这样创建的:如果WindowA可见,则显示WindowB,然后隐藏WindowA。当其中一个窗口变为可见时,该窗口的计时器将再次激活,从而恢复数据库调用,而另一个窗口的计时器将被禁用: private static void InterfaceChanger_Elapsed(object

我为一个服务台团队创建了一个墙板应用程序,前端使用WPF,后端使用Cisco手机数据库。该应用程序由两个显示不同信息的屏幕组成,这些屏幕显示在同一屏幕上,并通过
System.Timers.Timer

应用程序是这样创建的:如果
WindowA
可见,则显示
WindowB
,然后隐藏
WindowA
。当其中一个窗口变为可见时,该窗口的计时器将再次激活,从而恢复数据库调用,而另一个窗口的计时器将被禁用:

private static void InterfaceChanger_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    if (WindowA.Visibility == Visibility.Visible)
    {
        WindowAEnabled = false;
        ChangeVisibility(Visibility.Visible, WindowB);
        WindowBEnabled = true;
        WindowB_Elapsed(null, null); // force the call of the timer's callback
        ChangeVisibility(Visibility.Collapsed, WindowA);
    }
    else
    {
        WindowBEnabled = false;
        ChangeVisibility(Visibility.Visible, WindowA);
        WindowAEnabled = true;
        WindowA_Elapsed(null, null);  // force the call of the timer's callback
        ChangeVisibility(Visibility.Collapsed, WindowB);
    }
}

private static void ChangeVisibility(Visibility visibility, Window window)
{
    window.Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
    {
        window.Visibility = visibility;
    }, null);
}
问题是,这是完美的工作。。。最多90%的时间。问题是,有时,如果
WindowA的
可见性更改为可见,而
WindowB的
可见性更改为折叠,
WindowB
折叠,但
WindowA
需要2-3秒才能变为可见,大多数情况下,
WindowA
是可见的,而当
WindowB
折叠时,它是看不见的。这(当它不工作时)会导致桌面可见,而不是应用程序可见。
我最初使用的是
DispatcherPriority.Background
,但这导致屏幕变换器在70-80%的时间内工作,因此我决定将其更改为
DispatcherPriority.Normal
DispatcherPriority.Send
结果基本上与正常情况相同)

问题:

  • 考虑到调度程序在四核CPU的x64模式下运行,这是调度程序预期的正常行为吗
  • 知道查询是在不等待的异步方法中执行的,调度程序不应该优先于这些方法吗
  • 是否有其他方法(不使用Dispatcher或其他窗口属性)来完成我所寻找的任务
  • 这是用于访问/启动Windows的代码:

    //WindowA:
    <Application x:Class="MyNamespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="WindowA.xaml">
    
    //WindowA class:
    public static WindowA WindowAInstance;
    public WindowA()
    {
        // unnecessary code hidden
        WindowAInstance = this;
        WindowB b = new WindowB;
    }
    
    // WindowB class
    public static WindowB WindowBInstance;
    public WindowB()
    {
        // unnecessary code hidden
        WindowBInstance = this;
    }
    
    // this is the code that starts the timers
    public static void StartTimersHandling()
    {
        Database.RemoveAgents();
    
        InterfaceChangerTimer = new System.Timers.Timer();
        InterfaceChangerTimer.Interval = ApplicationArguments.InterfaceChangerTime;
        InterfaceChangerTimer.Elapsed += InterfaceChanger_Elapsed;
        InterfaceChangerTimer.AutoReset = true;
        InterfaceChangerTimer.Start();
    
        WindowATimer = new System.Timers.Timer();
        WindowATimer.Interval = 1000;
        WindowATimer.Elapsed += WindowATimer_Elapsed;
        WindowATimer.AutoReset = true;
        WindowATimer.Start();
    
        WindowBTimer = new System.Timers.Timer();
        WindowBTimer.Interval = 1000;
        WindowBTimer.Elapsed += WindowBTimer_Elapsed;
        WindowBTimer.AutoReset = true;
        WindowBTimer.Start();
    }
    
    //WindowA:
    //WindowA类:
    公共静态WindowA WindowA状态;
    公共窗口a()
    {
    //隐藏不必要的代码
    WindowAInstance=this;
    WindowB=新的WindowB;
    }
    //WindowB类
    公共静态WindowB WindowBinInstance;
    公共窗口b()
    {
    //隐藏不必要的代码
    WindowBInstance=这个;
    }
    //这是启动计时器的代码
    公共静态void StartTimersHandling()
    {
    Database.removagents();
    InterfaceChangerTimer=新的System.Timers.Timer();
    InterfaceChangerTime.Interval=ApplicationArguments.InterfaceChangerTime;
    InterfaceChangerTimer.Appead+=InterfaceChanger\u Appead;
    InterfaceChangerTimer.AutoReset=true;
    InterfaceChangerTimer.Start();
    WindowATimer=new System.Timers.Timer();
    WindowATimer.Interval=1000;
    WindowATimer.appeased+=WindowATimer\u appeased;
    WindowATimer.AutoReset=true;
    WindowATimer.Start();
    WindowBTimer=new System.Timers.Timer();
    窗口时间间隔=1000;
    WindowB计时器已用+=WindowB计时器已用;
    WindowBTimer.AutoReset=true;
    WindowBTimer.Start();
    }
    
    尝试使用
    Dispatcher.CurrentDispatcher
    而不是
    窗口。Dispatcher
    并开始激活:

    已更新 将计时器切换到Dispatchermer:


    听起来你在写一个信息亭应用程序(即全屏,非交互式)。如果是这样的话,我想你最好有一个单独的窗口,并在其中切换视图,而不是在两个单独的窗口之间切换。此外,还需要将数据库查询工作与窗口内容的刷新分开。此外,我认为如果视图之间互不了解会有所帮助:目前,第一个窗口与第二个窗口紧密耦合,这不是一个好主意

    在我看来,如果你稍微改变一下你的架构,你现在遇到的很多问题都会消失。以下是我的建议:

    首先,只需要一个窗口。创建两个用户控件(项目>添加用户控件),并将XAML布局从现有窗口移动到这两个新控件中。然后使主窗口看起来像这样:

    <Window x:Class="StackOverflow.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:my="clr-namespace:StackOverflow"
            WindowState="Maximized" WindowStyle="None">
        <Grid>
            <my:UserControl1 x:Name="_first" Panel.ZIndex="1" />
            <my:UserControl2 Panel.ZIndex="0" />
        </Grid>
        <Window.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                        <ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:5"
                            Storyboard.TargetName="_first"
                            Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                Value="{x:Static Visibility.Hidden}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Window.Triggers>
    </Window>
    
    正如您所看到的,它只是一个
    TextBlock
    元素,其
    Text
    属性绑定到用户控件的代码隐藏中定义的
    Number
    属性。我对两个用户控件使用了相同的XAML,只是改变了文本的垂直对齐方式,这样我就可以知道在任何给定时间哪个控件是可见的

    后面的代码如下所示(除类名外,两者都相同):

    它基本上包含一个由“worker”方法递增的
    Number
    属性(实现
    INotifyPropertyChanged
    )。worker方法由计时器调用:在这里,我使用的是
    dispatchermer
    ,但由于我没有直接更改任何UI元素,.NET计时器都会这样做


    工作线程计划使用
    Task.run
    在线程池上运行,然后异步运行。我正在模拟一个长时间运行的作业,使用
    Task.Delay
    等待一段时间。这个辅助方法就是调用数据库查询的地方。通过设置计时器的
    Interval
    属性,可以改变连续查询之间的间隔。没有什么可以说查询之间的间隔需要与UI的刷新间隔相同(即两个视图切换的速度);事实上,由于查询所需的时间不一,因此同步这两个数据将非常棘手。

    尝试使用Dispatcher.CurrentDispatcher而不是window.Dispatcher和priority DispatcherPriority.DataBind。那么,当WindowA的可见性发生变化时,会发生什么情况,是查询DB来更新它的视图吗?可能是db调用的问题,所以在收到db的响应之前不会显示windows?@mkonvisar在我使所有数据库调用异步之前都是如此
    timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
    timer.Tick += (sender, args) => InterfaceChanger_Elapsed();
    timer.Start();
    
    <Window x:Class="StackOverflow.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:my="clr-namespace:StackOverflow"
            WindowState="Maximized" WindowStyle="None">
        <Grid>
            <my:UserControl1 x:Name="_first" Panel.ZIndex="1" />
            <my:UserControl2 Panel.ZIndex="0" />
        </Grid>
        <Window.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                        <ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:5"
                            Storyboard.TargetName="_first"
                            Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                Value="{x:Static Visibility.Hidden}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Window.Triggers>
    </Window>
    
    <UserControl x:Class="StackOverflow.UserControl1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 Background="White" Padding="40">
        <TextBlock Text="{Binding Number}" FontSize="60"
            TextAlignment="Center" VerticalAlignment="Top" />
    </UserControl>
    
    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Controls;
    using System.Windows.Threading;
    
    namespace StackOverflow
    {
        public partial class UserControl1 : UserControl, INotifyPropertyChanged
        {
            public UserControl1()
            {
                InitializeComponent();
                DataContext = this;
    
                _timer = new DispatcherTimer
                    { Interval = TimeSpan.FromSeconds(5), IsEnabled = true };
                _timer.Tick += (sender, e) => Task.Run(async () => await DoWorkAsync());
            }
    
            readonly DispatcherTimer _timer;
            readonly Random _random = new Random();
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public int Number
            {
                get
                {
                    return _number;
                }
                private set
                {
                    if (_number != value)
                    {
                        _number = value;
                        if (PropertyChanged != null)
                        {
                            PropertyChanged(this, new PropertyChangedEventArgs("Number"));
                        }
                    }
                }
            }
            int _number;
    
            async Task DoWorkAsync()
            {
                // Asynchronous code started on a thread pool thread
    
                Console.WriteLine(GetType().Name + " starting work");
                _timer.IsEnabled = false;
                try
                {
                    await Task.Delay(TimeSpan.FromSeconds(_random.Next(4, 12)));
                    Number++;
                }
                finally
                {
                    _timer.IsEnabled = true;
                }
                Console.WriteLine(GetType().Name + " finished work");
            }
        }
    }