【问题标题】:Asynchronuos binding and LINQ query hangs异步绑定和 LINQ 查询挂起
【发布时间】:2013-07-31 20:50:39
【问题描述】:

我有一个将Enumerable 作为其绑定源的UI 控件。但是在我设置绑定源之前,我必须过滤我原来的Enumerable。我想为此目的使用 LINQ

control.BindingSource = from var item in enumerable.Cast<ItemType>()
                        where item.X == 1
                        select item;

这是一个 UI 挂起的问题,因为 enumerable 很慢(例如,如果像 yield return new Item(); Thread.Sleep(1000) ... 那样实现)并且控制尝试在 UI 线程中执行查询。我试图通过结合使用 Task 和 async-await 来解决这个问题:

control.BindingSource = await Task.Factory.StartNew(() =>
                            (from var item in enumerable.Cast<ItemType>()
                             where item.X == 1
                             select item).ToArray());

现在 UI 不会挂起,但查询执行完成后结果会立即可见。我通过在while 构造中使用ObservableCollectionEnumeratorawait 来解决这个问题:

var source = new ObservableCollection<object>();
control.BindingSource = source;

var enumerator = enumerable.GetEnumerator();
while (await Task.Factory.StartNew(() => enumerator.MoveNext()))
{
    var item = (ItemType)enumerator.Current;

    if (item.X == 1)
        source.Add(item);
}

我正在寻找至少允许使用 LINQ 的解决方案。 有什么想法吗?

【问题讨论】:

  • async 代码中,您应该更喜欢Task.Run 而不是StartNewhere's why.

标签: c# multithreading linq user-interface async-await


【解决方案1】:

不幸的是,async 不能很好地与 LINQ 配合使用。 Rx 团队有一个“异步枚举器”实验过,但我相信它已经被放弃了。

Task-based Asynchronous Pattern 确实有一个标准的进度报告方法,您可以在此处使用。

private static void EvaluateItems(IEnumerable<ItemType> items, IProgress<ItemType> progress)
{
  if (progress == null)
    return;
  var query = from var item in items where item.X == 1 select item;
  foreach (var result in query)
    progress.Report(result);
}


var source = new ObservableCollection<object>();
control.BindingSource = source;
var progress = new Progress<ItemType>(item => source.Add(item));
await Task.Run(() => EvaluateItems(enumerable.Cast<ItemType>(), progress);

基于IProgress 的代码具有更大的关注点分离。 EvaluateItems 方法只涉及枚举和过滤项目。它不必知道他们将进入ObservableCollection 或者它正在WPF 应用程序(Dispatcher) 中运行。所以它更便携和可测试。

【讨论】:

    【解决方案2】:

    试试这个方法:

    var query = from var item in enumerable.Cast<ItemType>()
                where item.X == 1
                select item;
    
    var source = new ObservableCollection<object>();
    control.BindingSource = source;
    
    Task.Factory.StartNew(
        () =>
        {
            foreach(var item in query)
            {
                Application.Current.Dispatcher.BeginInvoke(
                    new Action(() => source.Add(item)));
            }
        },
        TaskCreationOptions.LongRunning);
    

    【讨论】:

    • 不错的方法。我在任务开始之前添加了await,以防止方法继续执行,直到添加所有项目并将委托创建移到任务之外,以避免在每次迭代时创建新的 Action 实例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-27
    • 2020-01-18
    • 1970-01-01
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多