async eventhandler多次执行

时间:2013-12-28 15:15:03

标签: c# wpf task-parallel-library async-await deadlock

我有这个事件处理程序,可以多次执行。当我让它完成执行时,一切正常(只键入一个字符并等待直到计算结果)。 但是当我正常输入时,会发生死锁。至少我认为这是一个僵局。

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

2 个答案:

答案 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());