如何在应用程序退出之前重复执行一个方法而不影响 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
});
这是一个使用 DispatchTimer
和 async
/ 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>
我需要每 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
});
这是一个使用 DispatchTimer
和 async
/ 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>