UWP 从异步工作线程更新 UI

2024-03-08

我正在尝试实现一个长期运行的后台进程,定期报告其进度,以更新 UWP 应用程序中的 UI。我怎样才能做到这一点?我已经看到了几个有用的主题,但没有一个包含所有内容,而且我无法将它们全部放在一起。

例如,假设用户选择了一个非常大的文件,并且应用程序正在读取和/或操作文件中的数据。用户单击一个按钮,该按钮会使用用户选择的文件中的数据填充存储在页面上的列表。

PART 1

页面和按钮的单击事件处理程序如下所示:

public sealed partial class MyPage : Page
{
    public List<DataRecord> DataRecords { get; set; }

    private DateTime LastUpdate;

    public MyPage()
    {
        this.InitializeComponent();

        this.DataRecords = new List<DataRecord>();
        this.LastUpdate = DateTime.Now;

        // Subscribe to the event handler for updates.
        MyStorageWrapper.MyEvent += this.UpdateUI;
    }

    private async void LoadButton_Click(object sender, RoutedEventArgs e)
    {
        StorageFile pickedFile = // … obtained from FileOpenPicker.

        if (pickedFile != null)
        {
            this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);
        }
    }

    private void UpdateUI(long lineCount)
    {
        // This time check prevents the UI from updating so frequently
        //    that it becomes unresponsive as a result.
        DateTime now = DateTime.Now;
        if ((now - this.LastUpdate).Milliseconds > 3000)
        {
            // This updates a textblock to display the count, but could also
            //    update a progress bar or progress ring in here.
            this.MessageTextBlock.Text = "Count: " + lineCount;

            this.LastUpdate = now;
        }
    }
}

里面的MyStorageWrapper class:

public static class MyStorageWrapper
{
    public delegate void MyEventHandler(long lineCount);
    public static event MyEventHandler MyEvent;

    private static void RaiseMyEvent(long lineCount)
    {
        // Ensure that something is listening to the event.
        if (MyStorageWrapper.MyEvent!= null)
        {
            // Call the listening event handlers.
            MyStorageWrapper.MyEvent(lineCount);
        }
    }

    public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
    {
        List<DataRecord> recordsList = new List<DataRecord>();

        using (Stream stream = await file.OpenStreamForReadAsync())
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                while (!reader.EndOfStream)
                {
                    string line = reader.ReadLine();

                    // Does its parsing here, and constructs a single DataRecord …

                    recordsList.Add(dataRecord);

                    // Raises an event.
                    MyStorageWrapper.RaiseMyEvent(recordsList.Count);
                }
            }
        }

        return recordsList;
    }
}

我从以下获得的时间检查代码this https://stephenhaunts.com/2014/10/14/using-async-and-await-to-update-the-ui-thread/.

正如所写,此代码使应用程序对大文件没有响应(我在大约 850 万行的文本文件上进行了测试)。我以为添加async and await to the GetDataAsync()调用会阻止这种情况吗?这不是在 UI 线程之外的线程上工作吗?通过 Visual Studio 中的调试模式,我已验证程序正在按预期进行...它只是占用了 UI 线程,使应用程序无响应(请参阅Microsoft 提供的有关 UI 线程和异步编程的页面 https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/keep-the-ui-thread-responsive).

PART 2

我已经成功实现了一个异步、长时间运行的进程,该进程在单独的线程上运行,并且仍然定期更新 UI...但此解决方案不允许返回值 - 特别是第 1 部分中的行:

this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);

下面是我之前的成功实施(为简洁起见,省略了大部分内容)。有没有办法调整它以允许返回值?

In a Page class:

public sealed partial class MyPage : Page
{
    public Generator MyGenerator { get; set; }

    public MyPage()
    {
        this.InitializeComponent();

        this.MyGenerator = new Generator();
    }

    private void StartButton_Click(object sender, RoutedEventArgs e)
    {
        this.MyGenerator.ProgressUpdate += async (s, f) => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate ()
        {
            // Updates UI elements on the page from here.
        }

        this.MyGenerator.Start();
    }

    private void StopButton_Click(object sender, RoutedEventArgs e)
    {
        this.MyGenerator.Stop();
    }
}

并且在Generator class:

public class Generator
{
    private CancellationTokenSource cancellationTokenSource;

    public event EventHandler<GeneratorStatus> ProgressUpdate;

    public Generator()
    {
        this.cancellationTokenSource = new CancellationTokenSource();
    }

    public void Start()
    {
        Task task = Task.Run(() =>
        {
            while(true)
            {
                // Throw an Operation Cancelled exception if the task is cancelled.
                this.cancellationTokenSource.Token.ThrowIfCancellationRequested();

                // Does stuff here.

                // Finally raise the event (assume that 'args' is the correct args and datatypes).
                this.ProgressUpdate.Raise(this, new GeneratorStatus(args));
            }
        }, this.cancellationTokenSource.Token);
    }

    public void Stop()
    {
        this.cancellationTokenSource.Cancel();
    }
}

最后,还有两个支持类ProgressUpdate event:

public class GeneratorStatus : EventArgs
{
    // This class can contain a handful of properties; only one shown.
    public int number { get; private set; }

    public GeneratorStatus(int n)
    {
        this.number = n;
    }
}

static class EventExtensions
{
    public static void Raise(this EventHandler<GeneratorStatus> theEvent, object sender, GeneratorStatus args)
    {
        theEvent?.Invoke(sender, args);
    }
}

关键是要明白async/await并没有直接说等待的代码将在不同的线程上运行。当你这样做时await GetDataAsync(pickedFile);执行进入GetDataAsync方法仍在 UI 线程上并继续执行,直到await file.OpenStreamForReadAsync()已达到 - 这就是only实际上将在不同线程上异步运行的操作(如file.OpenStreamForReadAsync实际上是这样实现的)。

然而,有一次OpenStreamForReadAsync已完成(很快),await确保执行返回到它开始的同一个线程 - 这意味着用户界面线程。所以代码中实际昂贵的部分(读取文件while) 在 UI 线程上运行。

您可以通过使用来稍微改善这一点reader.ReadLineAsync,但是,每次之后您仍然会返回到 UI 线程await.

ConfigureAwait(false)

解决这个问题的第一个技巧是ConfigureAwait(false).

在异步调用上调用此方法会告诉运行时执行不必返回到最初调用异步方法的线程 - 因此这可以避免将执行返回到 UI 线程。把它放在你的箱子里的好地方是OpenStreamForReadAsync and ReadLineAsync calls:

public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
    List<DataRecord> recordsList = new List<DataRecord>();

    using (Stream stream = await file.OpenStreamForReadAsync().ConfigureAwait(false))
    {
        using (StreamReader reader = new StreamReader(stream))
        {
            while (!reader.EndOfStream)
            {
                string line = await reader.ReadLineAsync().ConfigureAwait(false);

                // Does its parsing here, and constructs a single DataRecord …

                recordsList.Add(dataRecord);

                // Raises an event.
                MyStorageWrapper.RaiseMyEvent(recordsList.Count);
            }
        }
    }

    return recordsList;
}

调度员

现在您释放了 UI 线程,但在进度报告方面又引入了另一个问题。因为现在MyStorageWrapper.RaiseMyEvent(recordsList.Count)在不同的线程上运行,你无法更新用户界面 in the UpdateUI直接方法,因为从非 UI 线程访问 UI 元素会引发同步异常。相反,您必须使用 UI 线程Dispatcher确保代码在正确的线程上运行。

在构造函数中获取对 UI 线程的引用Dispatcher:

private CoreDispatcher _dispatcher;

public MyPage()
{
    this.InitializeComponent();
    _dispatcher = Window.Current.Dispatcher;

    ...
}

提前这样做的原因是Window.Current再次只能从 UI 线程访问,但页面构造函数肯定在那里运行,因此它是理想的使用位置。

现在重写UpdateUI如下

private async void UpdateUI(long lineCount)
{
    await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
       // This time check prevents the UI from updating so frequently
       //    that it becomes unresponsive as a result.
       DateTime now = DateTime.Now;
       if ((now - this.LastUpdate).Milliseconds > 3000)
       {
           // This updates a textblock to display the count, but could also
           //    update a progress bar or progress ring in here.
           this.MessageTextBlock.Text = "Count: " + lineCount;

           this.LastUpdate = now;
       }
    });
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UWP 从异步工作线程更新 UI 的相关文章

随机推荐

  • Excel VBA 文本框时间验证为 [h]:mm

    我正在开发 VBA Excel 用户窗体 需要以 h mm 格式输入时间 这意味着小时数可以不受限制 并且不会像 hh mm 格式那样在 23 59 之后循环回 0 我在网上搜索过没有结果 这是我当前使用的代码 Private Sub Tx
  • 运行任务时显示 Gradle 控制台

    当我在 Android Stduio 中运行 gradle 任务时 如何显示 gradle 控制台 当我执行任务时 会弹出运行对话框 但控制台不会弹出 我希望能够看到 gradle 控制台 以便可以看到输出 但我不想永久看到 gradle
  • 通过多次变换替换和维护角色实体

    问题 我们的系统中存在各种格式的字符实体 例如 amp and amp amp 如果需要的话 我们需要将它们转换为标准 XML 字符实体 amp lt gt apos quot 然后通过几个单独的转换将它们维护为实体 给定 XML
  • MPI_Isend 和 MPI_Irecv 似乎导致死锁

    我在 MPI 中使用非阻塞通信在进程之间发送各种消息 然而 我似乎陷入了僵局 我用过PADB see here http padb pittman org uk 查看消息队列并得到以下输出 1 msg12 Operation 1 pendi
  • Windows 应用商店应用程序:更改应用程序栏按钮的图标?

    我想更改 Windows 应用商店应用程序中 AppBar 按钮的图标 我发现 AppBar 按钮具有如下所示的 XAML 标记
  • Websphere Liberty Profile 中的 Java EE-Timer / @Schedule

    我想使用计时器服务 特别是 Schedule注解 http docs oracle com javaee 6 api javax ejb Schedule html 在 WebSphere Liberty Profile 中 那可能吗 有一
  • 如何在WebBrowser Control中设置当前的document.domain以避免“访问被拒绝”?

    如何设置电流document domain在 WebBrowser Control 中避免跨域调用 XMLHTTP 请求或 Iframe frame 访问 中的 访问被拒绝 我试过了 CurrentDocument WebBrowserCo
  • codeigniter 活动记录,生成,但不执行查询

    我正在工作一个库 需要将 sql 查询作为字符串来完成其工作 我正在使用 CodeIgniter 及其数据库类的活动记录实现 我知道我可以像这样回显 SQL 语句 但我只想生成这个查询 而不是执行它 echo this gt db gt l
  • 如何更改android中的启动活动?

    我有两项活动 即login and calendar在我的应用程序中 目前我的startup活动是 calendar 我想运行login作为第一个活动不是calendar 启动活动 Launcher Activity 在项目的 Androi
  • Clojure 符号用作函数时有什么作用?

    在尝试解决 4Clojure 问题时 通用计算引擎 http www 4clojure com problem 121 涉及重新实现评估 我不小心最终调用了这样的内容 apply 16 8 而不是预期的 apply 16 8 这带来了返回的
  • 如何在组织模式下匹配/解析任务结束时的注释

    我正在寻找一种与我所说的 注释 相匹配的方法 注释 是组织任务的最后一行 或一组行 上面写着 These are the notes of the task 我真的写了一篇long正则表达式may完成这项工作 但我希望 org mode 已
  • mysql、转储、数据库恢复

    我已使用以下命令转储数据库 mysqldump uuser ppassword db name gt file 然后我完全删除了我的数据库 drop database db name 然后我创建了一个新数据库 create database
  • R8 从 AndroidStudio 中抛出 NullPointerException,但不会通过 gradlew 从命令行抛出

    Problem 当我单击 Android Studio 中的调试图标时 我从 R8 收到此 NullPointerException Executing tasks app assembleDebug in project Users ga
  • 显示多列复选框列表

    我目前显示的复选框列表如下 foreach var employee in Model Employees Html CheckBox employee Name br 如果您想要一长列复选框 这非常有用 但我的列表越来越长 所以我想将其显
  • 给定一组线段,找到面积最大的矩形

    想象一下我给了你一组如下形式的线段 x1 y1 x2 y2 我们有两个点定义了一条线段 就我们的目的而言 该部分始终是水平或垂直的 我想找到由线段包围的任何矩形的最大面积 例如 当给定以下线段集时 结果应为绿色阴影区域的面积 到目前为止 我
  • Android android.credentials.UNLOCK 初始化无密码密钥库

    有一个随机密钥通过 AES 加密本地凭据 我按照下面的教程尝试存储securely该密钥稍后可以解密 nelenkov blogspot co uk 在 Android 中存储应用程序秘密 http nelenkov blogspot jp
  • 线程池是如何工作的,以及如何在像 NodeJS 这样的 async/await 环境中实现它?

    我需要运行一个函数int f int i 有 10 000 个参数 由于 I O 时间的原因 执行大约需要 1 秒 在像Python这样的语言中 我可以使用线程 或者async await 我知道 但是我稍后会谈到 来并行化这个任务 如果我
  • 修订控制和托管比较[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我知道这个问题以前已经以不同的形式被问过很多次了 我是一个无知的人 因为我认为我可以在讨论中添加任何内
  • 如何显示大于手机屏幕尺寸的图像?

    我有一个问题 但我没有找到真正一致的答案 这就是为什么我向你们伸出援手 我一直在尝试从以下意义上为我的应用程序实现 取景器 效果 我有一张巨大的图片 我希望能够 飞 过 水平和垂直滚动 直到到达边界 到目前为止 对此 或类似问题 的所有流行
  • UWP 从异步工作线程更新 UI

    我正在尝试实现一个长期运行的后台进程 定期报告其进度 以更新 UWP 应用程序中的 UI 我怎样才能做到这一点 我已经看到了几个有用的主题 但没有一个包含所有内容 而且我无法将它们全部放在一起 例如 假设用户选择了一个非常大的文件 并且应用