首页 > 解决方案 > 无法理解如何在异步过程中使用 Progress(T) 更新 UI

问题描述

我已经在谷歌上搜索了好几个小时,并阅读了很多讨论这个问题的 SO 问题,但我很抱歉地说我只是不知道如何使用它。

基本上我想要做的是在异步任务运行时在 WPF/Win Forms 应用程序中显示以下内容:

加工 。

并每隔 1 秒添加一个点,直到我达到三个,然后从 1 重新开始,直到任务完成。

作为第一步,我只是尝试在每秒之后添加一个点,并尝试使用 IProgress 操作,但我唯一能够完成的事情要么什么都没有,要么标签在一个镜头中填充了点,另一个任务似乎在完成后运行。

接下来我尝试执行以下操作:

   private async void startButton_Click(object sender, RoutedEventArgs e)
   {
        resultsTextBox.Text = "Waiting for the response ...";

        startButton.IsEnabled = false;
        resultsTextBox.Clear();

        var task = SumPageSizesAsync();

        var progress = Task.Run(() =>
        {
           var aTimer = new System.Timers.Timer(1000);
           aTimer.Elapsed += OnTimedEvent;
           aTimer.AutoReset = true;
           aTimer.Enabled = true;

           void OnTimedEvent(object source, ElapsedEventArgs et)
           {
               if (!lblProgress.Dispatcher.CheckAccess())
               {
                   Dispatcher.Invoke(() =>
                   {
                       lblProgress.Content += ".";
                   });
               }
           }
       });

       await task;
       await progress;

       resultsTextBox.Text += "\r\nControl returned to startButton_Click.";

       startButton.IsEnabled = true;
}

但同样,标签只是在其他任务继续运行时立即填充点。

我从Microsoft Docs中获取了这个示例

更新:我现在尝试删除循环 while(!task.IsComplete) 这基本上使标签在第一个任务完成后开始更新。然后我尝试了以下操作:

var task = SumPageSizesAsync();
var progress = GetProgress();

await Task.WhenAll(SumPageSizesAsync(), GetProgress());

但是得到了相同的结果,标签在第一个任务结束后开始更新。

谢谢您的帮助。

标签: c#async-await

解决方案


“Progress(T)”是错误的模式。

这是使用 100% async/await代码执行此操作的 WPF 应用程序的代码,没有创建额外的线程。

它启动两个async任务。第一个模拟长时间运行的async过程。第二个启动另一个async Task,将第一个任务作为参数。它循环直到第一个任务完成,同时使用“...”模式更新标签。它等待Task.Delay控制动画速度。

这两个任务都放在一个列表中,我们await完成了这两个任务。

这一切都可以包含在一个ShowProgressUntilTaskCompletes将 worker 作为参数的方法(或扩展方法)中Task,这为您提供了一种易于重用的方法来显示任何Task.

MainWindow.xaml:

<Window
    x:Class="LongProcessDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <StackPanel Margin="100" Orientation="Vertical">
        <Button Click="StartProcess_OnClick" Content="Start" />
        <TextBlock
            Name="LoadingText"
            Padding="20"
            Text="Not Running"
            TextAlignment="Center" />
    </StackPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;

namespace LongProcessDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void StartProcess_OnClick(object sender, RoutedEventArgs e)
        {
            var longRunningTask = SimulateLongRunningTask();
            var spinner = ShowSpinner(longRunningTask);
            var tasks = new List<Task>
            {
                longRunningTask,
                spinner,
            };
            await Task.WhenAll(tasks);
        }

        private async Task ShowSpinner(Task longRunningTask)
        {
            var numDots = 0;
            while (!longRunningTask.IsCompleted)
            {
                if (numDots++ > 3) numDots = 0;
                LoadingText.Text = $"Waiting{new string('.', numDots)}";
                await Task.Delay(TimeSpan.FromSeconds(.5));
            }

            LoadingText.Text = "Done!";
        }

        private async Task SimulateLongRunningTask()
        {
            await Task.Delay(TimeSpan.FromSeconds(10));
        }
    }
}

这是它运行的记录,窗口交互证明 UI 没有被阻塞。

在此处输入图像描述


作为额外的奖励,我感到无聊并实现了我提到的扩展方法(具有超级特别的奖励,来自 C# 7 的“本地函数”功能!)。

public static class TaskExtensions
{
    public static async Task WithSpinner(this Task longRunningTask, TextBlock spinnerTextBox)
    {
        async Task ShowSpinner()
        {
            var numDots = 0;
            while (!longRunningTask.IsCompleted)
            {
                if (numDots++ > 3) numDots = 0;
                spinnerTextBox.Text = $"Waiting{new string('.', numDots)}";
                await Task.Delay(TimeSpan.FromSeconds(.5));
            }
            spinnerTextBox.Text = "Done!";
        }

        var spinner = ShowSpinner();
        var tasks = new List<Task>
        {
            longRunningTask,
            spinner,
        };
        await Task.WhenAll(tasks);
    }
}

你像这样使用它:

await SimulateLongRunningTask().WithSpinner(LoadingTextBlock);

推荐阅读