在重入调用上使用异步方法时,如何处理WPF中的忙指示?

时间:2018-09-07 03:34:05

标签: c# wpf async-await

在WPF控件(例如网格)中,我们通常可以设置一个布尔属性以显示控件正在忙于加载数据,在UI中这将导致“正在加载...”指示符。

使用async方法时,我们只需要确保在调用该方法之前将IsBusy = "true"旋转,将IsBusy="false"之后的await旋转即可。

但是,如果我可以在第一次调用完成时多次调用网格加载方法,那么即使在第二次调用正在进行时,它也会关闭繁忙指示器。

有什么办法解决这个问题?我可以设置一个用于存储请求数的全局计数器,并根据此全局变量的计数来设置指标的状态,但是这是一种肮脏的方法,如果我的代码中有多个异步事件,将无法缩放。

示例场景

在下图中,我可以搜索学生的姓名,对于我的服务,每个名称都会返回详细信息(标记等)并将其显示在第二个网格中。

我想在第二个网格正在等待数据时显示繁忙指示器(否则用户可能不知道程序是否在做任何事情)。

输入名称时,将调用以下方法:

想象GetStudentResults花费5秒(每次通话)。我在0秒输入名字,然后在3秒输入另一个名字。现在,在5秒时,第一个呼叫返回,并且它关闭了忙音指示器,而第二个名称的详细信息未检索到。这就是我要避免的事情。

private async void SearchName(string name)
{
    ResultDisplayGrid.IsBusy = true;
    await GetStudentResults();
    ResultDisplayGrid.IsBusy = false;
}

enter image description here enter image description here

3 个答案:

答案 0 :(得分:1)

请尝试将您的异步调用包装在try-finally块中,完成所有操作后,它将调用finally将IsBusy标志设置为false。

  private async void SearchName(string name)
    {
        ResultDisplayGrid.IsBusy = true;

            try{
                await GetStudentResults();
            }
            finally{
                ResultDisplayGrid.IsBusy = false;
            }
    }

答案 1 :(得分:1)

自从最近发表评论以来,就一直在考虑这一点,因此它需要一个涉及适当任务管理的更复杂的解决方案,并且在协助他人时这超出了我的舒适范围。

我认为最快,最简单的方法是一旦搜索开始就阻止用户与文本框或GUI进行交互,从而防止在前一个搜索完成之前进行其他搜索。当然,这意味着用户将需要等待每个搜索完成才能开始下一个搜索。

我的下一个方法是存储GetStudentResults任务并使用CancellationToken。例如SearchName可能变为:

private CancellationTokenSource ctsSearch;
private Task tSearch;

private async void SearchName(string name)
{
    if(ctsSearch != null)
    {
        ctsSearch.Cancel();

        if(tSearch != null)
            await tSearch;
    }

    ctsSearch = new CancellationTokenSource();

    ResultDisplayGrid.IsBusy = true;
    tSearch = GetStudentResults(ctsSearch.Token);
    await tSearch;
    ResultDisplayGrid.IsBusy = false;

}

在上面的代码中,我们将取消上一个任务,然后尝试再次运行GetStudentResults。在您的GetStudentResults方法中,您将需要找到可以插入的位置:

if(token.IsCancellationRequested)
    return Task.FromResult(false); //Replace this return type with whatever suits your GetStudentResults return type.

我的GetStudentResults方法是:

private Task<bool> GetStudentResults(CancellationToken token)
{
    for(int i = 0; i < 10000; i++)
    {
        if (token.IsCancellationRequested)
            return Task.FromResult(false);

        Console.WriteLine(i);
    }
    return Task.FromResult(true);
}

有人可能还有其他想法,但对我来说,这是最简单的方法。

答案 2 :(得分:0)

您将需要使用CancellationTokenSource获得令牌,您可以跟踪该令牌是否通过重新输入取消了任务。

private CancellationTokenSource tokenSource;

public async void Search(string name)
{
    this.tokenSource?.Cancel();
    this.tokenSource = new CancellationTokenSource();
    var token = this.tokenSource.Token;

    this.IsBusy = true;
    try
    {
        // await for the result from your async method (non void)
        var result = await this.GetStudentResults(name, token);

        // If it was cancelled by re-entry, just return
        if (token.IsCancellationRequested)
        {
            return;
        }

        // If not cancelled then stop busy state
        this.IsBusy = false;
        Console.WriteLine($"{name} {result}");
    }
    catch (TaskCanceledException ex)
    {
        // Canceling the task will throw TaskCanceledException so handle it
        Trace.WriteLine(ex.Message);
    }
}

如果令牌将IsCancellationRequested设置为true,那么您的GetStudentResults也应考虑令牌并停止后台处理。