在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;
}
尝试在try-finally块中包装你的异步调用,当一切都完成后,它调用finally来将IsBusy标志设置为false。
private async void SearchName(string name)
{
ResultDisplayGrid.IsBusy = true;
try{
await GetStudentResults();
}
finally{
ResultDisplayGrid.IsBusy = false;
}
}
自最新评论以来考虑到这一点,它将需要一个更复杂的解决方案,涉及适当的任务管理,并在协助其他人时开始超出我的舒适区。
我认为最快最简单的方法是在搜索开始后阻止用户与文本框或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);
}
有人可能会有其他想法,但对我来说这些是最简单的方法。
您需要使用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应该考虑令牌并停止正在进行的后台处理。