【问题标题】:Queuing ObservableCollection Updates排队 ObservableCollection 更新
【发布时间】:2011-12-09 04:48:22
【问题描述】:

我正在编写一个 TAPI 应用程序,它使用状态模式来处理 TK 可能处于的不同状态。传入和传出呼叫通过 ObservableCollection 记录在 ListView(呼叫日志)中。呼叫数据与存储在 SQL-Server 数据库中的联系人进行比较,以确定可能的匹配项。然后使用该信息来更新呼叫日志。当然,所有这些都是实时的,并且都由 FSM(有限状态机)的不同状态控制/控制。

为了区分呼叫,我确实使用了呼叫 ID(由 TAPI 提供)。当电话响起或我开始呼出时,包括其呼叫 ID 的新记录将添加到呼叫日志中,并在客户数据库中搜索该号码,并相应更新日志中的某些数据。当处理不同的呼叫状态时,应用程序会动态更新日志(即更改可视化显示特定呼叫状态的图标等)。

ObservableCollection 的这些更新确实让我头疼,因为它们需要按特定顺序进行。例如,当接收到呼叫时,关联状态将在ObservableCollection 中创建一个新条目。当状态更改时,新状态可能会尝试更新集合,即使天气不清楚,要更改的条目已经添加。状态切换得非常快,显然比更新集合要快。

某种消息队列会是一个可能/好的解决方案吗?如果是这样,如何实现这样的消息队列 - 在状态机或ObservableCollection 的上下文中。我不是在寻找完整的解决方案,但如果我无法通过 google 或 stackoverflow 轻松找到任何信息,我将不胜感激。

编辑:大大改写了问题。

编辑:我为这个问题添加了自己的解决方案,但会等待看看是否有人有更好的想法。

【问题讨论】:

  • 顺便说一句,您需要将break 添加到您的foreach 以使其等同于FirstOrDefault
  • 哦,当然,谢谢。编辑我的帖子以反映该信息。虽然它不会改变这种特殊情况下的结果。
  • 改写问题以使主要问题更清楚(因为它现在对我来说更清楚了。

标签: c# message-queue observablecollection state-machine


【解决方案1】:

你检查过FirstOrDefault的结果是不是null?如果集合中不存在具有给定 id 的元素,则会发生这种情况。

例如:

var element = this.FirstOrDefault(p => p.ID == id);
if (element != null) {
    // Do something with element.Number.
}

或者你可以打电话给First,看看你是否收到InvalidOperationException

--- 编辑---

我从您的评论中看到,您似乎正在同时从多个线程访问相同的ObservableCollection。如果是这种情况,则需要通过锁定来保护共享数据结构。一个线程完全有可能在另一个线程正在搜索新元素的同时开始插入一个新元素,从而导致各种未定义的行为。根据MSN documentation for ObservableCollection

“不保证任何实例成员都是线程安全的。”

至于调试,您可以“冻结”其他线程,因此您可以只专注于感兴趣的线程,而不会过度“跳跃”。查看线程面板、右键菜单、冻结和解冻选项。

【讨论】:

  • 应用程序实际上永远不会走那么远,异常会在您的示例的第一行引发(嗯,正是在 LINQ 查询上)。正如我所提到的,this 是自定义类的 ObservableCollection。会不会是如果该类的某个成员为 null FirstOrDefault 会抛出 NullReferenceException(即使它不是 ID 属性)?我想我需要编写一个小型测试应用程序来看看是否是这种情况。
  • @SaschaHennig 您应该可以直接在委托中设置断点 (p => p.ID == id)。集合中的每个元素都会受到影响,但您最终会找到有问题的元素,并希望能够诊断它。
  • 我在调试时遇到问题的原因是应用程序的线程过多并且在线程之间跳转——这也是调用日志中的值在另一个线程尝试验证它之前没有更新的原因。应该在我的第一篇文章中更清楚地说明线程问题。尽管如此 +1 没有你的帖子,我可能不会像现在这样在解决问题的过程中走得那么远。我想我会实现某种更新呼叫日志的队列。
  • 虽然我当时没有使用这种(更简单,因此更好)的方法,因为我非常喜欢实现命令模式的想法,但这实际上是一个很好且有效的答案。命令模式实际上只是一种更复杂的方式,并不是真正的锁定,而是阻塞线程执行(关于我的实现)。我绝对应该早点标记这个答案。那时我还是很高兴自己走自己的路,因为我学到了很多东西,这并没有改变上面提到的事实。所以迟到总比没有好。谢谢!
【解决方案2】:

更新ObservableCollection 是一个长期运行的过程,至少与接收和处理TAPI 事件相比是这样。这可能导致竞争条件,其中必须编辑调用条目的调用状态无法找到该条目,因为它在实际必须添加调用的调用状态之前获得了写入/更新集合的锁定。此外,不按正确顺序处理 TAPI 事件会破坏状态机。

我决定实现一个简化的Command Pattern。用于触发性能繁重状态事务的 TAPI-Events 被添加到线程安全、非阻塞和可观察的命令队列中。当命令入队时,队列类开始在新线程中“执行”(和出队)命令,也就是说,它在有限状态机中触发正确的调用状态,直到队列中没有命令。如果有一个出队线程已经在运行,则不会创建新线程(多线程会再次导致竞争条件)并且队列类正在阻止重入以确保在任何时候都只会执行一个命令。

所以基本上:所有 TAPI 事件(invoker)都按照它们发生的顺序尽快添加到队列(client)中。然后队列将 TAPI 信息中继到 接收器,即执行业务逻辑的有限状态机,它会花时间但确保信息以正确的顺序更新。

编辑:从 .NET 4.0 开始,您可以使用 ConcurrentQueue(T) Class 来实现相同的结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-31
    • 2015-01-31
    相关资源
    最近更新 更多