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

问题描述 投票:0回答:3

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

当使用async方法时,我们必须确保在调用方法之前转换IsBusy = "true"并在IsBusy="false"之后调用await

但是,如果我可以在第一次呼叫完成时多次调用电网负载方法,即使第二次呼叫正在进行,它也会关闭忙指示灯。

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

示例场景

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

我希望在第二个网格等待数据时显示忙碌指示符(否则用户可能不知道程序是否正在执行任何操作)。

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

想象一下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

c# wpf async-await
3个回答
1
投票

尝试在try-finally块中包装你的异步调用,当一切都完成后,它调用finally来将IsBusy标志设置为false。

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

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

1
投票

自最新评论以来考虑到这一点,它将需要一个更复杂的解决方案,涉及适当的任务管理,并在协助其他人时开始超出我的舒适区。

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

我的下一个方法是存储GetStudentResults Task并使用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);
}

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


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);
    }
}

此外,如果token.IsCancellationRequested设置为true,您的GetStudentResults应该考虑令牌并停止正在进行的后台处理。

© www.soinside.com 2019 - 2024. All rights reserved.