C# 如何让任务返回到WPF中的UI线程
背景:我在C#方面比较有经验,但对WPF完全陌生 我有一个WPF应用程序,将在内部用于一些简单的监视。我有一个数据库调用,然后返回的数据显示在树视图中, 当这种情况发生时,在数据返回之前,有一个覆盖显示为“加载…”。我当前的实现如下所示:C# 如何让任务返回到WPF中的UI线程,c#,wpf,async-await,task,C#,Wpf,Async Await,Task,背景:我在C#方面比较有经验,但对WPF完全陌生 我有一个WPF应用程序,将在内部用于一些简单的监视。我有一个数据库调用,然后返回的数据显示在树视图中, 当这种情况发生时,在数据返回之前,有一个覆盖显示为“加载…”。我当前的实现如下所示: await WithOverlay("Loading...", async () => { MyControl.Items = await _database.Retrieve(messageSummary.Id); }); private a
await WithOverlay("Loading...", async () =>
{
MyControl.Items = await _database.Retrieve(messageSummary.Id);
});
private async Task WithOverlay(string overlayMessage, Func<Task> func)
{
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
await func();
Overlay.Visibility = Visibility.Hidden;
}
其中,WithOverlay
如下所示:
await WithOverlay("Loading...", async () =>
{
MyControl.Items = await _database.Retrieve(messageSummary.Id);
});
private async Task WithOverlay(string overlayMessage, Func<Task> func)
{
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
await func();
Overlay.Visibility = Visibility.Hidden;
}
我的想法是:
- 创建一个延迟250毫秒的任务,该任务完成后将显示覆盖
- 等待包装的函数
- 当包装函数完成时,如果覆盖任务尚未完成,则取消它
- 隐藏覆盖(无论其当前状态如何)
Overlay.Content=overlayMessage
行中,我得到一个异常“调用线程无法访问此对象,因为它属于另一个线程。”
我怀疑这与任务的同步上下文有关(如果我正确地回忆起我的技术演示),但我不知道如何控制它以获得继续
在同一线程上继续。您可以将
任务调度器
与继续任务关联,以强制在UI线程上设置内容
和可见性
属性的代理:
var overlayTask = Task.Delay(250, token).ContinueWith(_ => {
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
您可以将
TaskScheduler
与延续任务关联,以强制在UI线程上设置内容
和可见性
属性的代理:
var overlayTask = Task.Delay(250, token).ContinueWith(_ => {
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
谢谢你的回答,@mm8。好的,我是这样完成的,我很想听听你对这是否比你的anwser更糟糕的想法:
private async Task WithOverlay(string overlayMessage, Func<Task> func)
{
var delayTask = Task.Delay(250);
var wrappedTask = func();
var completedTask = await Task.WhenAny(delayTask, wrappedTask);
if (completedTask == delayTask)
{
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
}
await wrappedTask;
Overlay.Visibility = Visibility.Hidden;
}
private async Task with overlay(字符串overlayMessage,Func Func)
{
var delayTask=任务延迟(250);
var wrappedTask=func();
var completedTask=wait Task.wheny(delayTask,wrappedTask);
if(completedTask==delayTask)
{
Overlay.Content=overlayMessage;
Overlay.Visibility=可见性.Visibility;
}
等待包装任务;
Overlay.Visibility=Visibility.Hidden;
}
因此,我启动了一个仅延迟250毫秒的任务。我启动包装任务,然后使用任务查看哪一个首先完成;如果延迟任务先完成,则显示
覆盖层。然后我等待包装任务并隐藏覆盖层。谢谢你的回答,@mm8。好的,我是这样完成的,我很想听听你对这是否比你的anwser更糟糕的想法:
private async Task WithOverlay(string overlayMessage, Func<Task> func)
{
var delayTask = Task.Delay(250);
var wrappedTask = func();
var completedTask = await Task.WhenAny(delayTask, wrappedTask);
if (completedTask == delayTask)
{
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
}
await wrappedTask;
Overlay.Visibility = Visibility.Hidden;
}
private async Task with overlay(字符串overlayMessage,Func Func)
{
var delayTask=任务延迟(250);
var wrappedTask=func();
var completedTask=wait Task.wheny(delayTask,wrappedTask);
if(completedTask==delayTask)
{
Overlay.Content=overlayMessage;
Overlay.Visibility=可见性.Visibility;
}
等待包装任务;
Overlay.Visibility=Visibility.Hidden;
}
因此,我启动了一个仅延迟250毫秒的任务。我启动包装任务,然后使用任务查看哪一个首先完成;如果延迟任务先完成,则显示
覆盖层。然后我等待wrappedTask并隐藏覆盖层。您可以尝试使用Dispatcher
而不是tasks,或者使用Dispatcher。调用来进行编组。您必须同时等待funct
和覆盖层Task
,WhenAny()。但我认为使用计时器更干净。这是关于async/Wait的最好解释,它确实帮助了我,也可能帮助了你:@bommelding谢谢你的评论。我很好奇你如何用定时器来完成这项工作,你介意详细说明一下吗?当你完成这项工作后,一个需要260毫秒的数据库调用怎么样?您在这里并没有真正解决任何问题,只是将问题转移了一下。您可以尝试使用dispatchermer
而不是任务,或者使用Dispatcher.Invoke
进行编组。您必须同时等待func
和overlytask
,使用whenay()。但我认为使用计时器更干净。这是关于async/Wait的最好解释,它确实帮助了我,也可能帮助了你:@bommelding谢谢你的评论。我很好奇你如何用定时器来完成这项工作,你介意详细说明一下吗?当你完成这项工作后,一个需要260毫秒的数据库调用怎么样?你在这里没有真正解决任何问题,只是把问题转移了一下。你的问题是“如何让任务返回WPF中的UI线程”,这就是我的答案。你的问题是“如何让任务返回WPF中的UI线程”,这就是我的答案。