【问题标题】:How to get a Task to return to the UI thread in WPF如何让任务返回到 WPF 中的 UI 线程
【发布时间】:2019-02-10 11:04:12
【问题描述】:

背景:我在 C# 方面比较有经验,但对 WPF 完全陌生。

我有一个 WPF 应用程序,它将在内部用于一些简单的监控。我有一个数据库调用,然后返回的数据显示在树视图中, 当这种情况发生时,在数据返回之前会有一个覆盖显示“正在加载...”。我当前的实现如下所示:

await WithOverlay("Loading...", async () =>
{
    MyControl.Items = await _database.Retrieve(messageSummary.Id);
});

WithOverlay 看起来像这样:

private async Task WithOverlay(string overlayMessage, Func<Task> func)
{
    Overlay.Content = overlayMessage;
    Overlay.Visibility = Visibility.Visible;

    await func();

    Overlay.Visibility = Visibility.Hidden;
}

现在,这工作得很好,但是因为很多时候(取决于用户在应用程序的其他地方查看的具体内容)数据库调用真的回来了 很快,“正在加载...”叠加层只是令人讨厌的闪烁。这只是一件小事,但它困扰着我,所以我决定我可以通过放一个小东西来解决它 在叠加层出现之前延迟;比如说,一刻钟。这是我尝试修改WithOverlay 方法:

private async Task WithOverlay(string overlayMessage, Func<Task> func)
{
    var tokenSource = new CancellationTokenSource();
    var token = tokenSource.Token;

    var overlayTask = Task.Delay(250, token).ContinueWith(_ => {
        Overlay.Content = overlayMessage;
        Overlay.Visibility = Visibility.Visible;
    });

    await func();

    if (token.CanBeCanceled) tokenSource.Cancel();

    Overlay.Visibility = Visibility.Hidden;
}

我的想法是:

  • 创建一个延迟为 250 毫秒的任务,完成后将显示叠加层
  • 等待包装函数
  • 当被包装的函数完成时,如果覆盖任务还没有完成则取消它
  • 隐藏叠加层(无论其当前状态如何)

很遗憾,虽然这不起作用;在Overlay.Content = overlayMessage 行上,我收到异常“调用线程无法访问此对象,因为另一个线程拥有它。'”。

我怀疑这与任务的同步上下文有关(如果我没记错我的技术演示),但我不知道如何控制它以获得继续 在同一线程上继续。

【问题讨论】:

  • 您可以尝试DispatcherTimer 代替任务或使用Dispatcher.Invoke 进行编组。
  • 您必须同时等待funcoverlayTask,使用WhenAny()。但我认为使用 Timer 更清洁。
  • 恕我直言,这是对 async/await 的最佳解释,它确实对我有所帮助,也可能对您有所帮助:youtube.com/watch?v=0qiB3oW_nd8
  • @bommelding 感谢您的评论。我很好奇你将如何使用计时器来实现这一点,你介意详细说明一下吗?
  • 当你完成这个工作后,一个需要 260 毫秒的数据库调用怎么样?您在这里并没有真正解决任何问题,只是将问题转移了。

标签: c# wpf async-await task


【解决方案1】:

您可以将TaskScheduler 与延续任务相关联,以强制设置ContentVisibility 属性的委托在UI 线程上设置:

var overlayTask = Task.Delay(250, token).ContinueWith(_ => {
    Overlay.Content = overlayMessage;
    Overlay.Visibility = Visibility.Visible;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

【讨论】:

    【解决方案2】:

    感谢您的回答,@mm8。好的,所以我是这样完成的,我很想听听您对这是否比您的答案更糟糕的想法:

        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;
        }
    

    所以,我开始了一个只有 250 毫秒延迟的任务。我开始打包任务,然后使用Task.WhenAny 查看哪个先完成;如果延迟任务首先完成然后我显示 叠加层。然后我等待 WrappedTask 并隐藏叠加层。

    【讨论】:

    • 您的问题是“如何让任务返回到 WPF 中的 UI 线程”,这就是我的回答。
    猜你喜欢
    • 1970-01-01
    • 2019-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多