c# - 无法理解如何在异步过程中使用 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;
}
但同样,标签只是在其他任务继续运行时立即填充点。
更新:我现在尝试删除循环 while(!task.IsComplete) 这基本上使标签在第一个任务完成后开始更新。然后我尝试了以下操作:
var task = SumPageSizesAsync();
var progress = GetProgress();
await Task.WhenAll(SumPageSizesAsync(), GetProgress());
但是得到了相同的结果,标签在第一个任务结束后开始更新。
谢谢您的帮助。
解决方案
“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);
推荐阅读
- javascript - 如何使用 javascript setinterval 方法定期刷新剑道网格
- python - Scrapy:如何在单独的函数中加载多个项目?
- python - 如何暂停 python 的 for 循环,直到 Arduino 响应
- xml - cvc-elt.1:找不到元素“hazelcast”的声明
- pdf - ps2pdf - 无法打开初始设备
- java - android SDK 源代码在哪里创建了 protobuf 编码的 AndroidManifest.xml 文件?
- c# - 如何在不添加额外 \ 参数的情况下传递加密密码?
- php - 如何识别和删除所有未分配给任何帖子/自定义帖子/页面以及重复的图像/pdf
- cobol - 如何删除证明结束符号?
- angular - 如何等待所有流结果并将最终结果作为对象返回?