我有这个事件处理程序,可以多次执行。当我让它完成执行时,一切正常(只键入一个字符并等待直到计算结果)。 但是当我正常输入时,会发生死锁。至少我认为这是一个僵局。
private async void tbInput_TextChanged(object sender, TextChangedEventArgs e)
{
resultStackPanel.Children.Clear();
List<Task<UIElement>> tasks = new List<Task<UIElement>>();
if (tbInput.Text != "")
{
foreach (IModule mod in Modules)
{
if (mod.IsApplicable(tbInput.Text))
tasks.Add(mod.CalculateOutcome(tbInput.Text));
}
while (tasks.Count > 0)
{
await Task.WhenAny(tasks);
foreach (Task<UIElement> resultTask in tasks)
{
if (resultTask.Status == TaskStatus.RanToCompletion)
{
if (resultTask.Result != null)
{
resultStackPanel.Children.Add(resultTask.Result);
}
tasks.Remove(resultTask);
break;
}
}
}
}
}
我很确定这是因为这一行而且我应该取消所有任务,但我不知道如何,因为CancellationToken是无用的,因为执行繁重工作的库不支持它:
await Task.WhenAny(tasks);
答案 0 :(得分:3)
我可以看到您的代码存在一些问题。首先,如果任何任务未成功完成,您将获得无限循环。另一方面,您现有的任务仍将完成并尝试更新您的UI。
由于您的库不支持CancellationToken
,因此您实际上无法取消操作(这很糟糕)。但是你至少可以假装取消它们,允许它们运行完成然后忽略结果。您可以使用我称之为asynchronous callback contexts的技术。
将这样的逻辑拆分为另一种方法更容易,而不是使用延续;像这样的东西:
private object _callbackContext;
private async void tbInput_TextChanged(object sender, TextChangedEventArgs e)
{
_callbackContext = new object();
resultStackPanel.Children.Clear();
if (tbInput.Text == "")
return;
Modules.Where(mod => mod.IsApplicable(tbInput.Text))
.Select(mod => ApplyModuleAsync(mod));
}
private async Task ApplyModuleAsync(IModule module)
{
var myContext = _callbackContext;
var element = await module.CalculateOutcome(tbInput.Text);
if (myContext != _callbackContext || element == null)
return;
resultStackPanel.Children.Add(element);
}
答案 1 :(得分:0)
您只需向正在调用的异步方法添加一个延续,然后异步等待所有这些方法完成Task.WhenAll
:
foreach (IModule mod in Modules)
{
if (mod.IsApplicable(tbInput.Text))
{
tasks.Add(mod.CalculateOutcome(tbInput.Text).ContinueWith(resultTask =>
{
if (resultTask.Result != null)
{
resultStackPanel.Children.Add(resultTask.Result);
}
}, TaskContinuationOptions.OnlyOnRanToCompletion));
}
}
await Task.WhenAll(tasks.ToArray());