我有两个默认窗口。 我希望一个窗口启动一个工作,以模态(对话框)形式显示另一个窗口(进度指示,但现在它并不重要),然后在完成这项工作后关闭它。我有我的实施中存在以下问题:
1)工作完成后("完成!"消息框显示,但它也不重要,只是指示),ProgressWindow不会自动关闭;
2)如果我通过手动点击红叉来关闭它,System.InvalidOperationException带有消息"调用线程无法访问此对象,因为另一个线程拥有它。"发生在
行await task;
3)Continue方法中的代码实际上是在Go方法完成之前执行的 - 为什么?
我怎样才能达到这样的行为?
我的实施:
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Window w = new ProgressWindow();
var task =
Task
.Run(() => Go())
.ContinueWith(completedTask => w.Close());
w.ShowDialog();
await task; // InvalidOperationException throws
}
async protected void Go()
{
await Task.Delay(500); // imitate some work
MessageBox.Show("Completed!"); // indicate that work has been completed
}
}
}
答案 0 :(得分:1)
此处无需使用延续,只需坚持await
即可。不仅如此,而且因为您使用了async void
,程序在关闭窗口之前没有等待半秒钟。
此外,在这种情况下使用Task.Run
确实没有任何好处,因为Go
已经可以等待。
改进和简化的代码是:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Window w = new ProgressWindow();
Task work = Go(w);
w.ShowDialog();
await work; // exceptions in unawaited task are difficult to handle, so let us await it here.
}
async Task Go(Window w)
{
await Task.Delay(500);
w.Close();
}
}
您收到错误的原因是Task.Run
创建的任务(继续)在非UI线程上执行,并且不允许访问UI(w.Close();
)非UI线程。
如果您的工作受益于Task.Run,您可以像这样修改Go()
方法:
async Task Go(Window w)
{
await Task.Run(() => {
// ... heavy work here
});
w.Close();
}
答案 1 :(得分:0)
您试图从后台线程关闭窗口... 如果你仍然想使用Task.Run()。ContinueWith(),那么你应该使用Dispatcher来关闭窗口。但最好使用async \ await语法。
async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Window w = new ProgressWindow();
var task = Task.Run(() => Go()).ContinueWith(completedTask =>
{
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Send, new Action(() =>
{
w.Close();
}));
});
w.ShowDialog();
await task;
}
答案 2 :(得分:0)
我阅读了Peter Bons的回答并重新思考它 - 他的方法使用异步Go方法(当然是在我的问题中)然后在没有Task.Run的情况下等待它的结果。我来到的另一个变体是基于Peter Bons和Andrew Shkolik的答案。我通过Task.Run调用同步方法Go异步并使用Dispatcher来操作窗口。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Window w = new ProgressWindow();
Task work = Task.Run(() => Go(w));
w.ShowDialog();
await work;
}
void Go(Window w)
{
Thread.Sleep(2000); // imitate some work
Dispatcher.BeginInvoke(
new Action(() =>
{
w.Close();
}));
}
}
答案 3 :(得分:0)
这是扩展方法ShowDialogUntilTaskCompletion
,可以代替内置ShowDialog
使用。提供的Task
完成后,它将自动关闭窗口。如果任务异常完成,则窗口仍将关闭,并且该方法返回时不会抛出异常。
如果通过其他方式(例如,用户单击窗口的关闭按钮或按Alt + F4)关闭了窗口,则此方法也会返回。因此,不能保证该方法返回时任务会完成。
public static class WindowExtensions
{
public static bool? ShowDialogUntilTaskCompletion(this Window window,
Task task, int minDurationMsec = 500)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (task == null) throw new ArgumentNullException(nameof(task));
if (minDurationMsec < 0)
throw new ArgumentOutOfRangeException(nameof(minDurationMsec));
var closeDelay = Task.Delay(minDurationMsec);
HandleTaskCompletion();
return window.ShowDialog();
async void HandleTaskCompletion()
{
try
{
await Task.Yield(); // Ensure that the completion is asynchronous
await task;
}
catch { } // Ignore exception
finally
{
try
{
await closeDelay;
window.Close();
}
catch { } // Ignore exception
}
}
}
}
作为奖励,它还接受一个minDurationMsec
参数,以使窗口在指定的最小持续时间内保持可见(以使窗口在眨眼时不会关闭)。
用法示例:
async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Window w = new ProgressWindow();
var task = Task.Delay(2000); // Simulate some asynchronous work
w.ShowDialogUntilTaskCompletion(task);
try
{
// Most likely the task will be completed at this point
await task;
}
catch (Exception ex)
{
// Handle the case of a faulted task
}
}
答案 4 :(得分:-1)
async (p) =>
{
await Task.Run(() => {
if (p == null) return;
//code
}).ConfigureAwait(true);
p.Close();
});