我已经搜索了好几个小时,并且阅读了很多讨论该问题的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;
}
但是,在其他任务继续运行的同时,标签又一次填充了点。
为例更新: 我现在尝试删除了while(!task.IsComplete)循环,该循环基本上使标签在第一个任务完成后开始更新。然后我尝试执行以下操作:
var task = SumPageSizesAsync();
var progress = GetProgress();
await Task.WhenAll(SumPageSizesAsync(), GetProgress());
但是得到相同的结果,标签在第一个任务完成后开始更新。
谢谢您的帮助。
答案 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没有被阻止。
作为一个额外的奖励,我感到无聊并实现了我提到的扩展方法(带有 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中安排一个更新方法。“
https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model