使用Async/Await时的WPF ProgressBar用法

4

我有一个非常简单的任务,需要加载一些数据,如下所示:

async Task<List<Invoice>> GetInvoices()
{
    var invoices = await Task.Run(() => db.Invoices.AsNoTracking().ToList());
    return invoices;
}

如何将它与进度条一起使用最佳?我不需要获取已加载的百分比(虽然这可能有用),只需在加载数据开始和结束时显示/隐藏进度条。也许可以创建一个“完成”方法吗?

我对async / await非常陌生,所以请手把手教!


4
但是,如果您正在使用await等待任务完成,只需在开始时将进度条的状态设置为不确定,然后在await之后停止它。 - Patrick
通常我会将回调函数传递给后台任务/工作线程,以便它可以使用该回调函数来报告进度,并在UI线程上安排进度指示器的更新。但是,由于您的任务只有一个语句,我不确定您是否有方便的方法来表示增量进度。也许最好像@Patrick建议的那样使用不确定的等待指示器。 - Mike Strobel
你应该使用 ToListAsync 异步执行查询,而不是在另一个线程中同步执行查询。 - Servy
3个回答

13
假设在某个UI事件处理程序中调用了GetInvoices,并且有一个名为ProgressBar的进度条控件,这非常简单:

假设GetInvoices在某个UI事件处理程序中被调用,并且有一个叫做ProgressBar的进度条控件,它非常简单:

private async void OnButton1Click(..)
{
    ProgressBar.IsIndeterminate = true;
    var result = await GetInvoices();
    ProgressBar.IsIndeterminate = false; // Maybe hide it, too
    // TODO: Do stuff with result
}

如果您想显示实际进度,请查看Progress<T>IProgress<T>


2

您已经拥有所需的东西。为了说明,这里有一个简单的例子,涉及任务、await和ContinueWith。

using System;
using System.Threading.Tasks;
using System.Diagnostics;
public class Program
{
    public static void Main()
    {
        Task t = new Program().TestTasks();
        Console.WriteLine("hello");
        t.Wait();
    }

    public async Task TestTasks()
    {
        Stopwatch watch = Stopwatch.StartNew();
        Console.WriteLine("1." + watch.ElapsedMilliseconds);
        // await task
        int res = await TestAsync();
        Console.WriteLine("2." + watch.ElapsedMilliseconds + " " + res);
        // save task and wait on it
        Task<int> t = TestAsync();
        t.ContinueWith((r) =>
        {
            Console.WriteLine("4." + watch.ElapsedMilliseconds + " " + r.Result);
        });
        Console.WriteLine("3." + watch.ElapsedMilliseconds);
        t.Wait();
    }

    public static async Task<int> TestAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        return 42;
    }
}

这将打印:

1.0
hello
2.2009 42
3.2009
4.4013 42
请注意,使用 await 会使方法离开并在 Main 方法中打印“hello”,然后在 TestTasks 方法中继续(大约)2 秒。另一方面,如果我们不使用 await(如对 TestAsync 的第二次调用所示),3. 将立即被打印,然后在 2 秒后,4. 被打印。
考虑到这一点,您可以只需使用 await 调用名为 GetInvoices 的方法,因为您正在返回一个 Task。
// Set state of progressbar to indeterminate
List<Invoice> result = await GetInvoices();
// Set state of progressbar to nothing

1
你可以这样做:

var task = Task.Run(() => db.Invoices.AsNoTracking().ToList()); 

//show progress

task.ContinueWith(...CLOSE PROGRESS BAR...);

在实践中,您使用Task.ContinueWith结构来捕获任务的终止并隐藏之前显示的进度条。
注意:请记住,从另一个线程操作UI元素需要调用Invoke(),因为该调用将实际调用重定向到主线程,这是唯一可以在窗口上操作UI控件的线程。

这意味着进度条不应该以模态方式显示?希望这符合OP的要求。 - King King
我理解 //show progress 是代码中显示进度条对话框的占位符。因此,如果以模态方式显示它,task.ContinueWith 就不会被执行。当然,如果在那里创建一个新线程,那就没问题了。 - King King
我没有给这个答案投反对票。看起来你没有理解我的意思。我的意思是,我理解你代码中的“//show progress”注释是一个占位符,例如这段代码progressBarDialog.ShowDialog();。然后这将阻塞当前线程,task.ContinueWith不会被调用。我认为显示进度条对话框的代码可以放在task.ContinueWith之后。 - King King
@KingKing:如果你阅读了关于Task.Complete的文档,它会说:“创建一个在目标任务完成时异步执行的继续项。”因此它将被调用。 - Tigran
您是指在此代码片段中 someWindow.ShowDialog(); task.ContinueWith(some_task);,当 someWindow 模态显示时,执行可以跳转到 task.ContinueWith 吗?这对我来说很奇怪。至少当前线程的执行会在 .ShowDialog() 处被阻塞,关闭该对话框后,执行可以跳转到 task.ContinueWith - King King
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接