技术速递|调用异步功能 - WinForms 在 .NET 9 中的未来发展

作者: Klaus Loeffelmann
排版:Alan Wang

随着 .NET 的不断发展,WinForms 开发者可用的工具也在不断进步,这使得开发更加高效且应用响应更迅速。在 .NET 9 中,我们很高兴引入了一系列新的异步 API,这些 API 大大简化了 UI 管理任务。从更新控件到显示窗体和对话框,这些新增功能以全新的方式将异步编程的强大功能引入到 WinForms 中。在本文中,我们将深入探讨四个关键 API,解释它们的工作原理、适用场景以及如何开始使用它们。

认识新的异步 API

.NET 9 专门为 WinForms 引入了几种异步 API,使得在异步场景中进行 UI 操作变得更加直观和高效。这些新增功能包括:

  • Control.InvokeAsync – 在 .NET 9 中全面发布的 API,有助于异步调用调用 UI 线程。

  • Form.ShowAsync 和 Form.ShowDialogAsync(实验性) – 这些 API 允许开发者以异步方式显示窗体,在复杂的 UI 场景中极大简化操作。

  • TaskDialog.ShowDialogAsync(实验性) – 该 API 提供了一种异步显示那些基于任务对话框的消息对话框控件的方法,特别适用于长时间运行的与 UI 绑定的操作。

接下来,我们将从 InvokeAsync 开始逐一解析这些 API。

Control.InvokeAsync:无缝异步 UI 线程调用

InvokeAsync 提供了一种强大的,可在不阻塞调用线程的情况下将调用传递给UI线程的方法。此方法允许在 UI 线程上执行同步和异步回调,提供了灵活性,并防止意外的“即发即弃”行为。它通过将操作排入 WinForms 主消息队列来实现,确保它们在 UI 线程上执行。这种行为类似于 Control.Invoke,后者也会将调用调度到 UI 线程,但两者之间有一个重要区别:InvokeAsync 不会阻塞调用线程,因为它是将委托发布到消息队列中,而不是直接发送。

Wait – 发送与发布?消息队列?

让我们分解这些概念,阐明它们的含义,以及为什么 InvokeAsync 的方法可以帮助改善应用程序的响应性。

在 WinForms 中,所有的 UI 操作都发生在主 UI 线程上。为了管理这些操作,UI 线程运行一个循环,称为消息循环(message loop),该循环会持续处理消息——例如按钮点击、屏幕重绘以及其他操作。这个循环是 WinForms 能够在处理指令的同时对用户操作保持响应的核心。当您使用现代 API 时,大多数应用程序代码并不是运行在这个 UI 线程上的。理想情况下,UI 线程应该仅用于那些必须更新UI的操作。然而,在某些情况下,代码不会自动运行在 UI 线程上。例如,当您启动一个独立的任务以并行执行计算密集型操作时,就会发生这种情况。在这些情况下,您需要将代码执行“调度”到 UI 线程,这样 UI 线程才能更新界面。否则就会出现以下情况:
在这里插入图片描述
假设我不被允许进入某个房间取一杯牛奶,而你可以。在这种情况下,只有一个选择:因为我不可能变成你,所以我只能请求你帮我取那杯牛奶。这与线程调度是一样的。工作线程不能变成 UI 线程,但代码的执行(取牛奶)可以被调度。换句话说,工作线程可以请求 UI 线程代表它执行某些代码。简单来说,这通过将一个方法的委托排入消息队列中来实现。

说到这里,让我们解决发送和发布的困惑:在消息循环中排队操作有两种主要方式:

发送消息(阻塞):Control.Invoke 使用这种方式。当调用 Control.Invoke 时,它会将指定的委托同步发送到 UI 线程的消息队列。这是一个阻塞操作,意味着调用线程会等待 UI 线程处理完该委托后才能继续。这在调用代码依赖于 UI 线程立即返回结果时非常有用,但如果过度使用,尤其是在处理长时间运行的操作时,可能导致 UI 卡顿。

发布消息(非阻塞):InvokeAsync 将委托发布到消息队列,这是一个非阻塞操作。这种方式告诉 UI 线程将操作排入队列,并尽快处理,但调用线程无需等待操作完成。方法会立即返回,使调用线程可以继续其工作。这种区别在异步场景中尤为重要,因为它允许应用程序同时处理其他任务而不产生延迟,从而最大限度地减少 UI 线程的瓶颈。

这里是一个简单比较:

操作方法阻塞描述
发送Control.Invoke在 UI 线程上调用委托,并等待其完成。
发布Control.InvokeAsync将委托排入 UI 线程的队列,并立即返回。

为什么这很重要

通过使用 InvokeAsync 发布委托,您的代码现在可以将多个更新排队到控件上,执行后台操作,或等待其他异步任务,而无需阻塞主 UI 线程。这种方法不仅有助于防止“冻结的 UI”体验,还能保持应用程序的响应性,即使在处理大量与 UI 绑定的任务时也能保持流畅。

总结:Control.Invoke 会等待 UI 线程完成委托(阻塞),InvokeAsync 会将任务交给 UI 线程,并立即返回(非阻塞)。这种差异使得 InvokeAsync 非常适合异步场景,让开发者能够构建更流畅、更具响应性的 WinForms 应用程序。

以下是每个 InvokeAsync 重载的工作方式:

public async Task InvokeAsync(Action callback, CancellationToken cancellationToken = default)
public async Task<T> InvokeAsync<T>(Func<T> callback, CancellationToken cancellationToken = default)
public async Task InvokeAsync(Func<CancellationToken, ValueTask> callback, CancellationToken cancellationToken = default)
public async Task<T> InvokeAsync<T>(Func<CancellationToken, ValueTask<T>> callback, CancellationToken cancellationToken = default)

每个重载都允许不同的同步和异步方法组合,可以选择是否带有返回值:InvokeAsync(Action callback, CancellationToken cancellationToken = default) 用于没有返回值的同步操作。如果您想在 UI 线程上更新控件的属性——例如设置 Label 的 Text 属性——这个重载允许您做到这一点,而无需等待返回值。回调会被发布到消息队列,并异步执行,返回一个 Task,如果需要,您可以等待该任务的完成。

await control.InvokeAsync(() => control.Text = "Updated Text");
InvokeAsync(Funccallback, CancellationToken cancellationToken = default) 

用于返回类型为 T 的同步操作。使用它可以在 UI 线程上计算并获取一个值,例如从 ComboBox 中获取 SelectedItem。InvokeAsync 将回调发布到 UI 线程,并返回一个 Task,允许您等待结果的完成。

int itemCount = await control.InvokeAsync(() => comboBox.Items.Count);
InvokeAsync(Funccallback, CancellationToken cancellationToken = default)

这个重载用于不返回结果的异步操作。它非常适用于较长时间运行的异步操作,更新 UI 的场景,例如等待数据加载完成后再更新控件。回调接收一个 CancellationToken 以支持取消,并需要返回一个 ValueTask,InvokeAsync 会(内部)等待该任务完成,同时保持 UI 在操作异步执行时的响应性。因此,实际上有两个“等待”发生:InvokeAsync 被等待(或者说可以被等待),同时您传递的 ValueTask 也会被内部等待。

await control.InvokeAsync(async (ct) =>
{await Task.Delay(1000, ct);  // Simulating a delaycontrol.Text = "Data Loaded";
});
InvokeAsync(Func<cancellationtoken, valuetask> callback, CancellationToken cancellationToken = default)

最后是用于返回类型为 T 的异步操作的重载版本。当一个异步操作必须在 UI 线程上完成并返回一个值时使用,例如在延迟后查询控件的状态或获取数据以更新 UI。回调接收一个 CancellationToken 并返回一个 ValueTask,InvokeAsync 会等待该任务完成并提供结果。

var itemCount = await control.InvokeAsync(async (ct) =>
{await Task.Delay(500, ct);  // Simulating data fetching delayreturn comboBox.Items.Count;
});

快速决策:选择正确的重载

  • 对于没有返回值的同步操作,使用 Action。

  • 对于有返回值的同步操作,使用 Func。

  • 对于没有结果的异步操作,使用 Func<CancellationToken, ValueTask>。

  • 对于有结果的异步操作,使用 Func<cancellationtoken, valuetask>。

使用正确的重载有助于在异步 WinForms 应用程序中平滑处理 UI 任务,避免主线程瓶颈,并提升应用程序的响应性。

以下是一个简单的例子:

var control = new Control();// Sync action
await control.InvokeAsync(() => control.Text = "Hello, async world!");// Async function with return value
var result = await control.InvokeAsync(async (ct) =>
{control.Text = "Loading...";await Task.Delay(1000, ct);control.Text = "Done!";return 42;
});

混淆异步和同步重载——真的会发生吗?

由于有许多重载选项,可能会误将异步方法传递给同步重载,从而导致意外的“即发即弃”行为。为了防止这种情况,WinForms 在 .NET 9 中引入了一种专门的 WinForms 分析器,当将一个异步方法(例如返回 Task 的方法)传递给不带 CancellationToken 的 InvokeAsync 同步重载时,该分析器会检测到并触发警告。这有助于您在潜在问题引发运行时错误之前发现并纠正它们。

例如,传递一个不支持 CancellationToken 的异步方法可能会生成如下警告:

warning WFO2001: Task is being passed to InvokeAsync without a cancellation token.

此分析器确保异步操作被正确处理,从而在您的 WinForms 应用程序中保持可靠且响应迅速的行为。

实验性 API

除了 InvokeAsync,WinForms 在 .NET 9 中还引入了用于显示窗体和对话框的实验性异步选项。这些 API 仍处于实验阶段,但为开发者提供了更大的异步 UI 交互灵活性,例如文档管理和窗体生命周期控制。

Form.ShowAsync 和 Form.ShowDialogAsync 是新的方法,允许异步显示窗体。它们简化了多个窗体实例的处理,尤其适用于需要多个相同窗体类型实例的情况,例如在单独窗口中显示不同文档时。

以下是如何使用 ShowAsync 的基本示例:

var myForm = new MyForm();
await myForm.ShowAsync();

并且对于模态对话框,您可以使用 ShowDialogAsync:

var result = await myForm.ShowDialogAsync();
if (result == DialogResult.OK)
{// Perform actions based on dialog result
}

这些方法简化了异步窗体显示的管理,并帮助您在等待用户交互时避免阻塞 UI 线程。

TaskDialog.ShowDialogAsync

TaskDialog.ShowDialogAsync 是 .NET 9 中的另一个实验性 API,旨在提升对话框交互的灵活性。它提供了一种异步显示任务对话框的方法,非常适合涉及耗时操作或多步骤流程的场景。

以下是异步显示任务对话框的方法示例:

var taskDialogPage = new TaskDialogPage
{Heading = "Processing...",Text = "Please wait while we complete the task."
};var buttonClicked = await TaskDialog.ShowDialogAsync(taskDialogPage);

此 API 允许开发者异步显示对话框,从而释放 UI 线程,提供更流畅的用户体验。

异步 API 的实际应用

这些异步 API 为 WinForms 应用程序解锁了新的功能,特别是在多表单应用程序、MVVM 设计模式和依赖注入场景中。通过利用异步操作处理表单和对话框,您可以:

  • 在异步场景中简化表单生命周期管理,特别是当处理同一表单的多个实例时。

  • 支持 MVVM 和 DI 工作流,在 ViewModel 驱动的架构中,异步表单处理是有益的。

  • 避免 UI 线程阻塞,即使在执行密集操作时也能实现更具响应性的界面。

如果您对如何通过 Invoke.Async 彻底改变 WinForms 应用程序的 AI 驱动现代化感到好奇,那么请观看 .NET Conf 2024 的演讲,看看这些功能在实际场景中的实现!

“智能化”你的 WinForms 应用程序

这还不是全部——不要错过我们在另一场精彩讲座中深入探讨 .NET 9 中 WinForms 的所有新特性。深入了解并获得灵感!

如何从同步操作启动异步操作

在 UI 场景中,从同步上下文触发异步操作是很常见的。当然,我们都知道,最好避免使用 async void 方法。

为什么要避免这种做法?当你使用 async void 时,调用者无法等待或观察方法的完成。这可能导致未处理的异常或意外行为。async void 方法实际上是“即发即弃”,它们不受Task提供的标准错误处理机制的约束。这使得在大多数场景中调试和维护更加困难。

但是!这里有一个例外,那就是事件处理方法或具有“事件处理方法特征”的方法。事件处理方法不能返回 Task 或 Task,因此 async void 允许它们触发异步操作,而不会阻塞 UI 线程。然而,由于 async void 方法不可等待,异常很难被捕获。为了解决这个问题,你可以在事件处理方法内部的异步操作周围使用错误处理结构,比如 try-catch。这样,即使在这些特殊情况下,也能确保异常得到适当处理。

例如:

private async void Button_Click(object sender, EventArgs e)
{try{await PerformLongRunningOperationAsync();}catch (Exception ex){MessageBox.Show($"An error occurred: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}
}

在这里,由于事件处理程序的签名,async void 是不可避免的,但通过将等待的代码包装在 try-catch 中,我们可以安全地处理异步操作过程中可能发生的任何异常。

以下示例使用一个名为 SevenSegmentTimer 的7段显示控件,以典型的7段式显示方式显示一个计时器,精度为十分之一秒。它有几个方法来更新和动画内容:

public partial class TimerForm : Form
{private SevenSegmentTimer _sevenSegmentTimer;private readonly CancellationTokenSource _formCloseCancellation = new();public FrmMain(){InitializeComponent();SetupTimerDisplay();}[MemberNotNull(nameof(_sevenSegmentTimer))]private void SetupTimerDisplay(){_sevenSegmentTimer = new SevenSegmentTimer{Dock = DockStyle.Fill};Controls.Add(_sevenSegmentTimer);}override async protected void OnLoad(EventArgs e){base.OnLoad(e);await RunDisplayLoopAsyncV1();}private async Task RunDisplayLoopAsyncV1(){// When we update the time, the method will also wait 75 ms asynchronously._sevenSegmentTimer.UpdateDelay = 75;while (true){// We update and then wait for the delay.// In the meantime, the Windows message loop can process other messages,// so the app remains responsive.await _sevenSegmentTimer.UpdateTimeAndDelayAsync(time: TimeOnly.FromDateTime(DateTime.Now));}}
}

当我们运行这个程序时,我们可以在屏幕上的窗体中看到这个计时器:
在这里插入图片描述
异步方法 UpdateTimeAndDelayAsync 完全按字面意思执行:它更新控件中显示的时间,然后等待我们在前一行通过 UpdateDelay 属性设置的时间。

如您所见,这个异步方法 RunDisplayLoopAsyncV1 在窗体的 OnLoad 中启动。这是典型的做法,即如何从同步 void 方法中启动异步操作。

对于典型的 WinForms 开发者来说,乍一看这可能会显得有些奇怪。毕竟,我们在 OnLoad 中调用了另一个方法,而那个方法永远不会返回,因为它最终会进入一个无限循环。那么,在这种情况下,OnLoad 是否会完成呢?我们不是在这里阻塞应用程序吗?

这就是异步编程的亮点所在。尽管 RunDisplayLoopAsyncV1 包含一个无限循环,但它是以异步方式构造的。当在循环内部遇到 await 关键字时(例如 await _sevenSegmentTimer.UpdateTimeAndDelayAsync()),方法会将控制权返回给调用者,直到等待的任务完成。

在 WinForms 应用程序的上下文中,这意味着 Windows 消息循环可以继续处理事件,比如重新绘制 UI、处理按钮点击或响应键盘输入。由于 await 暂停了 RunDisplayLoopAsyncV1 的执行而没有阻塞 UI 线程,应用程序保持响应。

当 OnLoad 被标记为async时,它会在遇到 RunDisplayLoopAsyncV1 中的第一个 await 时完成。待任务完成后,运行时会从上次暂停的地方恢复执行 RunDisplayLoopAsyncV1。这一切发生时不会阻塞 UI 线程,实际上允许 OnLoad 立即 return,即使异步操作在后台继续执行。

后台执行?您可以将其视为将方法拆分成几个部分,就像一个虚拟的 WaitAsync-Initiator,它在第一个 await 解决后被调用。接着它启动一个后台运行的 WaitAsync-Waiter,直到等待期结束。然后,触发 WaitAsync-Callback,实际上要求消息循环重新进入调用并完成所有跟随该异步调用的操作。

因此,实际的代码路径大致如下:
在这里插入图片描述
最好的理解方式是将其与连续处理的两个鼠标点击事件进行比较,第一个鼠标点击触发了 RunDisplayLoopAsyncV1,而第二个鼠标点击对应于 WaitAsync 回调,进入该方法的“第3部分”,当延迟正好在等待时。

这个过程随后会对每个异步方法中的 await 进行重复。这就是为什么即使存在无限循环,应用程序也不会卡住。实际上,技术上来说,OnLoad 实际上是正常完成的,但每个 await 后的部分会被消息循环在稍后的时间回调。

现在,我们仍然基本上只在 UI 线程上工作。(严格来说,回调会在短暂的时间内运行在线程池线程上,但我们暂时忽略这一点。)是的,我们是异步的,但到目前为止,并没有真正发生并行操作。直到现在,这更像是一个巧妙组织的接力赛,接力棒被无缝地传递给下一个选手,以至于根本不会有卡顿或阻塞。

但是,异步方法随时可以从不同的线程调用。如果我们在当前示例中这样做……

private async Task RunDisplayLoopAsyncV2(){// When we update the time, the method will also wait 75 ms asynchronously._sevenSegmentTimer.UpdateDelay = 75;// Let's kick-off a dedicated task for the loop.await Task.Run(ActualDisplayLoopAsync);// Local function, which represents the actual loop.async Task ActualDisplayLoopAsync(){while (true){// We update and then wait for the delay.// In the meantime, the Windows message loop can process other messages,// so the app remains responsive.await _sevenSegmentTimer.UpdateTimeAndDelayAsync(time: TimeOnly.FromDateTime(DateTime.Now));}}}

然后…
在这里插入图片描述

InvokeAsync 的重载解析的复杂性

如我们之前所学,这是一个很容易解决的问题,对吧?我们只是使用 InvokeAsync 来调用我们本地的函数 ActualDisplayLoopAsync,然后就完成了。那么,让我们这么做吧。我们获取 InvokeAsync 返回的 Task,然后将其传递给 Task.Run。轻松解决。在这里插入图片描述
好吧——看起来并不太好。我们遇到了两个问题。首先,如前所述,我们正在尝试调用一个返回 Task 的方法,但没有传递取消令牌。InvokeAsync 正在警告我们,在这种情况下我们正在设置一个“即发即弃”操作,而这个操作无法被内部等待。第二个问题不仅仅是警告,它还是一个错误。InvokeAsync 返回的是一个 Task,我们当然不能将其传递给 Task.Run。我们只能传递一个 Action 或返回 Task 的 Func,但绝不能直接传递一个 Task。不过,我们可以做的是将这一行转换为另一个本地函数,所以从这里……

// Doesn't work. InvokeAsync wants a cancellation token, and we can't pass Task.Run a task.
var invokeTask = this.InvokeAsync(ActualDisplayLoopAsync);// Let's kick-off a dedicated task for the loop.
await Task.Run(invokeTask);// Local function, which represents the actual loop.
async Task ActualDisplayLoopAsync(CancellationToken cancellation)

改为:

// This is a local function now, calling the actual loop on the UI Thread.
Task InvokeTask() => this.InvokeAsync(ActualDisplayLoopAsync, CancellationToken.None);await Task.Run(InvokeTask);async ValueTask ActualDisplayLoopAsync(CancellationToken cancellation=default)
...

现在它工作得非常顺利了!

为性能或目标代码流程进行并行化

我们的7段控制器还有一个巧妙的功能:分隔列的渐变动画。我们可以按以下方式使用这个功能:

private async Task RunDisplayLoopAsyncV4(){while (true){// We also have methods to fade the separators in and out!// Note: There is no need to invoke these methods on the UI thread,// because we can safely set the color for a label from any thread.await _sevenSegmentTimer.FadeSeparatorsInAsync().ConfigureAwait(false);await _sevenSegmentTimer.FadeSeparatorsOutAsync().ConfigureAwait(false);}}

当我们运行它时,结果看起来像这样:
在这里插入图片描述
然而,存在一个挑战:我们如何设置代码流程,使得运行时钟和渐变分隔符能够并行执行,并且都在一个连续的循环中?

为了实现这一目标,我们可以利用基于任务的并行性。

具体思路如下:

  • 同时运行时钟更新和分隔符渐变:我们异步执行这两个任务,并等待它们完成。

  • 妥善处理不同任务的时长:由于时钟更新和渐变动画可能需要不同的时间,我们使用 Task.WhenAny 来确保较快的任务不会延迟较慢的任务。

  • 重置已完成的任务:一旦某个任务完成,我们将其重置为 null,以便下一次迭代时重新启动该任务。

最终结果是:

private async Task RunDisplayLoopAsyncV6(){Task? uiUpdateTask = null;Task? separatorFadingTask = null;while (true){async Task FadeInFadeOutAsync(CancellationToken cancellation){await _sevenSegmentTimer.FadeSeparatorsInAsync(cancellation).ConfigureAwait(false);await _sevenSegmentTimer.FadeSeparatorsOutAsync(cancellation).ConfigureAwait(false);}uiUpdateTask ??= _sevenSegmentTimer.UpdateTimeAndDelayAsync(time: TimeOnly.FromDateTime(DateTime.Now),cancellation: _formCloseCancellation.Token);separatorFadingTask ??= FadeInFadeOutAsync(_formCloseCancellation.Token);Task completedOrCancelledTask = await Task.WhenAny(separatorFadingTask, uiUpdateTask);if (completedOrCancelledTask.IsCanceled){break;}if (completedOrCancelledTask == uiUpdateTask){uiUpdateTask = null;}else{separatorFadingTask = null;}}}protected override void OnFormClosing(FormClosingEventArgs e){base.OnFormClosing(e);_formCloseCancellation.Cancel();}

还有这个。在这个动画 GIF 中,您可以看到 UI 始终保持响应性,因为窗口可以通过鼠标平滑拖动。

总结

通过这些新的异步 API,.NET 9 为 WinForms 带来了先进的功能,使得处理异步 UI 操作变得更加容易。虽然一些 API,如 Control.InvokeAsync,已经准备好投入使用,但针对表单和对话框管理的实验性 API 为响应式 UI 开发提供了更多令人兴奋的可能性。

您可以在我们的 Extensibility-Repo 中的相应示例子文件夹找到本博客文章的示例代码。

通过 .NET 9 探索 WinForms 中异步编程的潜力,并确保在非关键项目中测试这些实验性功能。像往常一样,您的反馈至关重要,我们期待听到这些新的异步功能如何提升您的开发过程!

最后,祝编码愉快!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/498270.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker-构建自己的Web-Linux系统-镜像webtop:ubuntu-kde

介绍 安装自己的linux-server,可以作为学习使用&#xff0c;web方式访问&#xff0c;基于ubuntu构建开源项目 https://github.com/linuxserver/docker-webtop安装 docker run -d -p 1336:3000 -e PASSWORD123456 --name webtop lscr.io/linuxserver/webtop:ubuntu-kde登录 …

【每日学点鸿蒙知识】箭头函数、Watch状态变量、H5获取定位数据、前后台切换、长按事件

【每日学点鸿蒙知识】箭头函数、Watch状态变量、H5获取定位数据、前后台切换、长按事件 1、HarmonyOS confirm: () > void () > { }&#xff1f; confirm: () > void () > { }是什么格式。 是一个箭头函数&#xff0c;它的类型是 () > void&#xff0c;表示…

【人工智能机器学习基础篇】——深入详解监督学习之模型评估:掌握评估指标(准确率、精确率、召回率、F1分数等)和交叉验证技术

深入详解监督学习之模型评估 在监督学习中&#xff0c;模型评估是衡量模型性能的关键步骤。有效的模型评估不仅能帮助我们理解模型在训练数据上的表现&#xff0c;更重要的是评估其在未见数据上的泛化能力。本文将深入探讨监督学习中的模型评估方法&#xff0c;重点介绍评估指…

如何使用React,透传各类组件能力/属性?

在23年的时候&#xff0c;我主要使用的框架还是Vue&#xff0c;当时写了一篇“如何二次封装一个Vue3组件库&#xff1f;”的文章&#xff0c;里面涉及了一些如何使用Vue透传组件能力的方法。在我24年接触React之后&#xff0c;我发现这种扩展组件能力的方式有一个专门的术语&am…

点进CSS选择器

CSS 1.直接在标签的style属性进行设置(行内式) //写在数据单元格td标签内的stytle&#xff0c;设置color颜色和font-size字体大小&#xff1b; <td rowspan"3" style"color: red;font-size: 12px;">Web技术与应用</td> 2.写在head标签中的…

Python基于卷积神经网络的车牌识别系统开发与实现

1. 简介 车牌识别是人工智能在交通领域的重要应用&#xff0c;广泛用于高速违章检测、停车场管理和智能交通系统等场景。本系统通过基于卷积神经网络&#xff08;CNN&#xff09;的深度学习算法&#xff0c;结合 Python 和 MySQL 实现车牌的快速识别与管理。 系统特点&#x…

【PDF物流单据提取明细】批量PDF提取多个区域内容导出表格或用区域内容对文件改名,批量提取PDF物流单据单号及明细导出表格并改名的技术难点及小节

相关阅读及下载&#xff1a; PDF电子物流单据&#xff1a; 批量PDF提取多个区域局部内容重命名PDF或者将PDF多个局部内容导出表格&#xff0c;具体使用步骤教程和实际应用场景的说明演示https://mp.weixin.qq.com/s/uCvqHAzKglfr40YPO_SyNg?token720634989&langzh_CN扫描…

运行python程序报错 undefined symbol: ffi_type_uint32 的参考解决方法

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 ROS-Noetic 一、问题描述 运行 python 程序出现如下问题&#xff1a; Traceback (most recent call last):File "<string>", line 1, in <module&…

鱼眼相机模型与去畸变实现

1.坐标系说明 鱼眼相机模型涉及到世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换关系。对于分析鱼眼相机模型&#xff0c;假定世界坐标系下的坐标点,经过外参矩阵的变换转到相机坐标系&#xff0c;相机坐标再经过内参转换到像素坐标&#xff0c;具体如下 进一步进…

ASP.NET Core Web API Hangfire

ASP.NET Core Web API Hangfire 前言一、安装二、相关代码1.代码片段2.代码片段3.运行效果 三、测试代码1.即发即弃作业2.延迟作业3.重复作业4.延续作业5.页面调度作业 前言 &#x1f468;‍&#x1f4bb;&#x1f468;‍&#x1f33e;&#x1f4dd;记录学习成果&#xff0c;以…

DevOps实战:用Kubernetes和Argo打造自动化CI/CD流程(1)

DevOps实战&#xff1a;用Kubernetes和Argo打造自动化CI/CD流程&#xff08;1&#xff09; 架构 架构图 本设计方案的目标是在一台阿里云ECS服务器上搭建一个轻量级的Kubernetes服务k3s节点&#xff0c;并基于Argo搭建一套完整的DevOps CI/CD服务平台&#xff0c;包括Argo CD…

【openGauss】正则表达式次数符号“{}“在ORACLE和openGauss中的差异

一、前言 正则作为一种常用的字符串处理方式&#xff0c;在各种开发语言&#xff0c;甚至数据库中&#xff0c;都有自带的正则函数。但是正则函数有很多标准&#xff0c;不同标准对正则表达式的解析方式不一样&#xff0c;本次在迁移一个ORACLE数据库到openGauss时发现了一个关…

PCL点云库入门——PCL库点云滤波算法之半径滤波(RadiusOutlierRemoval)

1、算法原理 半径滤波算法是一种基于局部邻域的点云数据滤波方法。它通过设定一个半径阈值来确定一个球形邻域&#xff0c;对于点云中的每一个点&#xff0c;算法会检查其邻域内与其他点的位置。如果邻域内的点与中心点的距离小于或等于设定的半径阈值&#xff0c;那么这些点将…

LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读

LLMs之o3&#xff1a;《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 导读&#xff1a;2024年12月&#xff0c;这篇论文提出了一种名为“审慎式对齐 (Deliberative Alignment)”的新方法&#xff0c;旨在提高大型语言模型 (LLM) 的安全性。论…

分布式项目___某污水处理项目

一.分布式项目___污水处理项目 项目地址:https://gitee.com/yanyigege/collaborative-water-springboot.git ​ 1.项目背景 总公司在全国各地有处理污水的项目部,各项目部处理自己的污水,总部需要监控各地分项目部每天处理污水的原料用量,掌握各分部的污水处理情况 ​ 2.功…

PHP框架+gatewayworker实现在线1对1聊天--gatewayworker说明(2)

文章目录 gatewayworker使用说明onConnect 说明 gatewayworker使用说明 gatewayworker里只需要使用Applications\YourApp下的Events.php文件。 对文件的代码进行一下改造&#xff0c;如下&#xff0c;我们只需要用到onConnect方法&#xff0c;写法固定&#xff0c;其他方法都…

Java 同步锁性能的最佳实践:从理论到实践的完整指南

目录 一、同步锁性能分析 &#xff08;一&#xff09;性能验证说明 1. 使用同步锁的代码示例 2. 不使用同步锁的代码示例 3. 结果与讨论 &#xff08;二&#xff09;案例初步优化分析说明 1. 使用AtomicInteger原子类尝试优化分析 2. 对AtomicInteger原子类进一步优化 …

模型 10-10-10旁观思维

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。超脱当下&#xff0c;透视决策长远影响。 1 10-10-10旁观思维的应用 1.1 职业选择决策 背景&#xff1a;小张是一名大学毕业生&#xff0c;面对未来职业的选择感到迷茫。他擅长营销、策略和经济学&a…

Supermap iClient Webgl 粒子特效案例-消防场景

作者&#xff1a;Lzzzz 前言 WebGL 粒子特效的应用场景非常广泛&#xff0c;几乎可以在任何需要丰富视觉效果或动态表现的地方看到其身影。通过灵活运用颗粒系统&#xff0c;开发者可以创造出引人入胜的用户体验和视觉表现。 一、效果展示 二、实现步骤 1&#xff0c;构建…

使用MFC编写一个paddleclas预测软件

目录 写作目的 环境准备 下载编译环境 解压预编译库 准备训练文件 模型文件 图像文件 路径整理 准备预测代码 创建预测应用 新建mfc应用 拷贝文档 配置环境 界面布局 添加回cpp文件 修改函数 报错1解决 报错2未解决 修改infer代码 修改MFCPaddleClasDlg.cp…