gpt4 book ai didi

c# - 关闭所有子 WPF 窗口并终止等待代码

转载 作者:行者123 更新时间:2023-11-30 20:42:08 26 4
gpt4 key购买 nike

我正在尝试实现一个系统来关闭 WPF 应用程序中的所有模态和非模态窗口(主应用程序窗口除外)。当这些窗口关闭时,任何等待对话框结果的代码都应该是被遗弃。

到目前为止,我已经考虑/尝试过两种策略:

  1. 关闭并重新启动应用程序。
  2. 关闭所有窗口并依靠任务取消异常来放弃等待对话结果的所有代码。 (它冒泡到应用程序级别,然后被处理。)

第一个解决方案确实让应用程序关闭并且足以自动注销,但我对在等待的对话框关闭后继续执行的代码感到非常不舒服。是否有停止执行该代码的好方法?

第二个解决方案一直运行良好(调用代码被中止)但有一个严重缺陷:有时,模态和非模态窗口的某种组合快速连续关闭会导致应用程序锁定 ShowDialog 调用。 (至少,当您暂停执行时,这就是它结束的地方。)这很奇怪,因为断点清楚地表明 Closed 事件正在我打算关闭的所有窗口上引发。最终用户看到的结果是登录屏幕,无法单击但可以跳转。这么奇怪!尝试以不同的优先级调度调用均未成功,但 100 毫秒的 Task.Delay 可能已成功。 (不过,这不是真正的解决方案。)

如果每个打开的弹出窗口都在后台等待 TaskCompletionSource,并且在 TCS 完成后尝试使用调度程序对其自身调用 Close,为什么会一个(或多个)对话框仍然在 ShowDialog 上阻塞,即使在看到 Closed 事件被引发之后?有没有办法将这些调用正确地分派(dispatch)给 Close 以便它们成功完成?我需要特别注意窗口关闭的顺序吗?

一些伪代码-C#-混合示例:

class PopupService
{
async Task<bool> ShowModalAsync(...)
{
create TaskCompletionSource, publish event with TCS in payload
await and return the TCS result
}

void ShowModal(...)
{
// method exists for historical purposes. code calling this should
// probably be made async-aware rather than relying on the blocking
// behavior of Window.ShowDialog
create TaskCompletionSource, publish event with TCS in payload
rethrow exceptions that are set on the Task after completion but do not await
}

void CloseAllWindows(...)
{
for every known TaskCompletionSource driving a popup interaction
tcs.TrySetCanceled()
}
}

class MainWindow : Window
{
void ShowModalEventHandler(...)
{
create a new PopupWindow and set the owner, content, etc.
var window = new PopupWindow(...) { ... };
...
window.ShowDialog();
}
}

class PopupWindow : Window
{
void LoadedEventHandler(...)
{
...
Task.Run(async () =>
{
try
await the task completion source
finally
Dispatcher.Invoke(Close, DispatcherPriority.Send);
});

register closing event handlers
...
}

void ClosedEventHandler(...)
{
if(we should do something with the TCS)
try set the TCS result so the popup service caller can continue
}
}

最佳答案

使用 Window.ShowDialog,您可以创建一个嵌套的 Dispather 消息循环。使用 await,可以“跳转”到该内部循环并在那里继续逻辑执行 async 方法,例如:

var dialogTask = window.ShowDialogAsync();
// on the main message loop
await Task.Delay(1000);
// on the nested message loop
// ...
await dialogTask;
// expecting to be back on the main message loop

现在,如果 dialogTask 通过 TaskCompletionSource 完成,相应的 Window.ShowDialog() 调用返回到对于调用者,上述代码可能仍会在嵌套消息循环中结束,而不是在主核心消息循环中。例如,如果在对话框的 Window.Closed 事件处理程序中调用 TaskCompletionSource.SetResult/TrySetCanceled,或者在 Window.Close()< 之前/之后调用,则可能会发生这种情况 调用。这可能会产生不希望的重入副作用,包括死锁。

通过查看您的伪代码,很难判断死锁可能在哪里。令人担忧的是,您使用 Task.Run 只是为了等待在主 UI 线程上完成的任务,或者从池线程调用主 UI 线程上的同步回调(通过 Dispatcher.Invoke)。你当然不应该在这里需要Task.Run

出于类似目的,我使用以下版本的 ShowDialogAsync。它确保由嵌套 ShowDialogAsync 调用启动的任何内部消息循环此特定 ShowDialogAsync 任务完成之前退出:

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

namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}

// testing ShowDialogAsync
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var modal1 = new Window { Title = "Modal 1" };
modal1.Loaded += async delegate
{
await Task.Delay(1000);

var modal2 = new Window { Title = "Modal 2" };
try
{
await modal2.ShowDialogAsync();
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal2.Title);
}
};

await Task.Delay(1000);
// close modal1 in 5s
// this would automatically close modal2
var cts = new CancellationTokenSource(5000);
try
{
await modal1.ShowDialogAsync(cts.Token);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal1.Title);
}
}
}

/// <summary>
/// WindowExt
/// </summary>
public static class WindowExt
{
[ThreadStatic]
static CancellationToken s_currentToken = default(CancellationToken);

public static async Task<bool?> ShowDialogAsync(
this Window @this,
CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
var previousToken = s_currentToken;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(previousToken, token))
{
var currentToken = s_currentToken = cts.Token;
try
{
return await @this.Dispatcher.InvokeAsync(() =>
{
using (currentToken.Register(() =>
@this.Close(),
useSynchronizationContext: true))
{
try
{
var result = @this.ShowDialog();
currentToken.ThrowIfCancellationRequested();
return result;
}
finally
{
@this.Close();
}
}
}, DispatcherPriority.Normal, currentToken);
}
finally
{
s_currentToken = previousToken;
}
}
}
}
}

这允许您通过关联的 CancelationToken 取消最外层模态窗口,这将自动关闭任何嵌套模态窗口(那些使用 ShowDialogAsync) 并退出相应的消息循环。因此,您的逻辑执行流将在正确的外部 消息循环中结束。

请注意,它仍然不能保证关闭多个模态窗口的正确逻辑顺序,如果这很重要的话。但它保证多个嵌套的 ShowDialogAsync 调用返回的任务将以正确的顺序完成。

关于c# - 关闭所有子 WPF 窗口并终止等待代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31616023/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com