无法理解在异步过程中如何使用Progress(T)更新UI

时间:2018-12-14 20:47:45

标签: c# async-await

我已经搜索了好几个小时,并且阅读了很多讨论该问题的SO问题,但是很遗憾,我只是不知道如何使用它。

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

  

处理中。

每隔1秒添加一个点,直到我达到3,然后从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());

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

谢谢您的帮助。

3 个答案:

答案 0 :(得分:1)

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

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

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

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

所有这些都可以包装成一个以工作人员ShowProgressUntilTaskCompletes作为参数的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没有被阻止。

enter image description here


作为一个额外的奖励,我感到无聊并实现了我提到的扩展方法(带有 super 特殊奖励,这是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);

答案 1 :(得分:0)

如果使用await,则意味着您的代码将等待异步操作在该行完成,然后继续。 这就是为什么progress任务要等到task任务才开始执行的原因。 您可以创建一个与task并行运行的后台线程,直到完成为止,您可以在其中告诉UI线程每秒对点进行动画处理。由于未阻止UI线程(仅等待task完成),因此可以正常工作。

示例代码:

string originalLblContent = (lblProgress.Content as string) ?? "";
bool taskStarted = false;

var progressThread = new Thread((ThreadStart)delegate
{
    // this code will run in the background thread
    string dots = "";
    while(!taskStarted || !task.IsCompleted) {
        if(dots.Length < 3) {
            dots += ".";
        } else {
            dots = "";
        }
        // because we are in the background thread, we need to invoke the UI thread
        // we can invoke it because your task is running asynchronously and NOT blocking the UI thread
        Dispatcher.Invoke(() =>
        {
            lblProgress.Content = originalLblContent + dots;
        });
        Thread.Sleep(1000);
    }
});

progressThread.Start();
taskStarted = true;
await task;

// the task is now finished, and the progressThread will also be after 1 second ...

答案 2 :(得分:-1)

您的方法在这里有点时髦。在每个线程完成之前,await语句将阻止方法返回。等待功能不是完全异步执行(为什么呢?您有执行该请求的线程)。

您需要重新考虑解决问题的方法。从根本上讲,您想在另一个进程进行时更新UI。这需要多线程。

从Microsoft:

“在图形应用程序中处理阻塞操作可能很困难。我们不想从事件处理程序中调用阻塞方法,因为该应用程序似乎会冻结。我们可以使用单独的线程来处理这些操作,但是完成后,我们必须与UI线程同步,因为我们不能直接从工作线程中修改GUI,可以使用Invoke或BeginInvoke将委托插入到UI线程的Dispatcher中。最终,这些委托将被执行并有权修改UI元素。

在此示例中,我们模仿一个远程过程调用来检索天气预报。我们使用一个单独的工作线程来执行此调用,完成后,我们将在UI线程的Dispatcher中安排一个更新方法。“

enter image description here

https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model