如何在应用程序退出之前重复执行一个方法而不影响 GUI? C# WPF

How to repeatedly execute a method without affecting the GUI until application exits? C# WPF

我需要每 60 秒执行一个方法,直到应用程序退出而不减慢使用 C# WPF 的 GUI。

每 60 秒执行一次的方法检查数据库中的布尔值 Table。如果该值为真,应用程序将保留 运行,如果该值为假,则应用程序将退出。

我看到一些帖子使用 System.Threading.Timers、后台工作人员和任务。但是我不知道哪个是最好的选择,这样 GUI 仍然是交互式的

如有任何建议,我们将不胜感激

我们这里不是实时 OS,因此讨论计时器的精确性没有意义 - 它们从来都不是绝对精确的。但是,如果我们谈论的是 60 秒这样的时间粒度,那么无论如何都没有关系。

所以计时器可以是一个选项,但我更喜欢另一种方式:

public
MainWindow()
{
    InitializeComponent();

    var cts = new CancellationTokenSource();
    var task = CheckToClose(cts.Token);
    Closing += delegate { cts.Cancel(); };
}

async
Task CheckToClose(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        try { await Task.Delay(TimeSpan.FromSeconds(60), ct); } catch (TaskCanceledException) { return; }
        var keepRunning = await ShouldKeepRunning(ct);
        if (!keepRunning)
        {
            Close();
            return;
        }
    }
}

Task<bool> ShouldKeepRunning(CancellationToken ct) =>
    // it's important here to take a thread pool task here so that working with the DB
    // doesn't block the UI
    Task.Run<bool>(
        delegate
        {
            // here you ask the DB whether you keep running
            // if working with DB is potentially too slow,
            // then take the CancellationToken into account
        });

这是一个使用 DispatchTimerasync / await 的示例。这是 大多数 WPF 应用程序的首选计时器。在某些情况下您可能需要其他计时器,但这个计时器将处理大部分 WPF 任务。

DispatchTimer 允许代码在 UI 线程上 运行,因此您不必担心手动调度跨线程操作。

async / await 调用允许数据库操作(此处通过调用 Task.Delay 模拟)不阻塞 UI。您应该将 Task.Delay 替换为适当的异步数据库操作,例如 DbContext.SomeDbSet.FirstOrDefaultAsync() 如果您使用的是 Entity Framework.

XAML 仅显示一个允许应用程序在选中时退出的复选框,以及一个动画标签,以证明 UI 线程未被阻塞。


BetterWindow.xaml.cs:

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace asyncTest
{
    public partial class BetterWindow : Window
    {
        public BetterWindow()
        {
            InitializeComponent();

            var timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(10);
            timer.Tick += Timer_Tick;
            timer.Start();
        }

        private async void Timer_Tick(object sender, EventArgs e)
        {
            // "async void" is generally frowned upon, but it is acceptable for event handlers.
            if (await ShouldExit())
            {
                Close();
            }
        }

        private async Task<bool> ShouldExit()
        {
            Debug.WriteLine("Checking the DB");
            //Simulate a long DB operation
            await Task.Delay(TimeSpan.FromSeconds(5));
            return chkAllowClose.IsChecked.GetValueOrDefault(false);
        }
    }
}

BetterWindow.xaml:

<Window x:Class="asyncTest.BetterWindow"
        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:asyncTest"
        mc:Ignorable="d"
        Title="BetterWindow" Height="450" Width="800">
    <DockPanel>
        <CheckBox Content="Allow Close" Name="chkAllowClose" DockPanel.Dock="Top"></CheckBox>
        <Grid>
            <Label Content="The UI isn't locked!" RenderTransformOrigin="0.5, 0.5" Width="200" Height="200">
                <Label.RenderTransform>
                    <RotateTransform x:Name="noFreeze" />
                    </Label.RenderTransform>
                <Label.Triggers>
                    <EventTrigger RoutedEvent="Loaded">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                        Storyboard.TargetProperty="(Label.RenderTransform).(RotateTransform.Angle)"
                                        To="-360" Duration="0:0:10" RepeatBehavior="Forever" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Label.Triggers>
            </Label>
        </Grid>
    </DockPanel>
</Window>