包含非 null 元素的列表最终包含 null。同步问题?

发布于 2024-08-29 22:44:31 字数 1375 浏览 5 评论 0原文

首先,对这个标题感到抱歉——我想不出一个足够简短和清晰的标题。

问题是:我有一个列表 List; list 我总是向其中添加新创建的 MyClass 实例,如下所示:list.Add(new MyClass())。我不会以任何其他方式添加元素。

但是,然后我使用 foreach 迭代列表,发现有一些空条目。即下面的代码:

foreach (MyClass entry in list)
    if (entry == null)
         throw new Exception("null entry!");

有时会抛出异常。 我应该指出,list.Add(new MyClass()) 是从同时运行的不同线程执行的。我唯一能想到的解释 null 条目的方法是并发访问。毕竟,List 不是线程安全的。尽管我仍然觉得奇怪,它最终包含空条目,而不是不提供任何排序​​保证。

你还能想到其他原因吗?

另外,我不关心添加项目的顺序,并且我不希望调用线程阻塞等待添加其项目。如果同步确实是问题所在,您能否推荐一种简单的方法来异步调用 Add 方法,即创建一个委托来在我的线程继续运行其代码时处理该问题?我知道我可以为 Add 创建一个委托并对其调用 BeginInvoke 。这看起来合适吗?

谢谢。


编辑:基于Kevin建议的简单解决方案:

public class AsynchronousList<T> : List<T> {

    private AddDelegate addDelegate;
    public delegate void AddDelegate(T item);

    public AsynchronousList() {
        addDelegate = new AddDelegate(this.AddBlocking);
    }

    public void AddAsynchronous(T item) {
        addDelegate.BeginInvoke(item, null, null);
    }

    private void AddBlocking(T item) {
        lock (this) {
            Add(item);
        }
    }
}

我只需要控制Add操作,并且我只需要它进行调试(它不会出现在最终产品中),所以我只想快速修复。

谢谢大家的回答。

First of all, sorry about the title -- I couldn't figure out one that was short and clear enough.

Here's the issue: I have a list List<MyClass> list to which I always add newly-created instances of MyClass, like this: list.Add(new MyClass()). I don't add elements any other way.

However, then I iterate over the list with foreach and find that there are some null entries. That is, the following code:

foreach (MyClass entry in list)
    if (entry == null)
         throw new Exception("null entry!");

will sometimes throw an exception.
I should point out that the list.Add(new MyClass()) are performed from different threads running concurrently. The only thing I can think of to account for the null entries is the concurrent accesses. List<> isn't thread-safe, after all. Though I still find it strange that it ends up containing null entries, instead of just not offering any guarantees on ordering.

Can you think of any other reason?

Also, I don't care in which order the items are added, and I don't want the calling threads to block waiting to add their items. If synchronization is truly the issue, can you recommend a simple way to call the Add method asynchronously, i.e., create a delegate that takes care of that while my thread keeps running its code? I know I can create a delegate for Add and call BeginInvoke on it. Does that seem appropriate?

Thanks.


EDIT: A simple solution based on Kevin's suggestion:

public class AsynchronousList<T> : List<T> {

    private AddDelegate addDelegate;
    public delegate void AddDelegate(T item);

    public AsynchronousList() {
        addDelegate = new AddDelegate(this.AddBlocking);
    }

    public void AddAsynchronous(T item) {
        addDelegate.BeginInvoke(item, null, null);
    }

    private void AddBlocking(T item) {
        lock (this) {
            Add(item);
        }
    }
}

I only need to control Add operations and I just need this for debugging (it won't be in the final product), so I just wanted a quick fix.

Thanks everyone for your answers.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我为君王 2024-09-05 22:44:31

List 只能同时支持多个读取器。如果要使用多个线程添加到列表中,则需要首先锁定对象。实际上没有办法解决这个问题,因为如果没有锁,您仍然可以让某人从列表中读取数据,同时另一个线程更新它(或者多个对象也尝试同时更新它)。

http://msdn.microsoft.com/en-us/library/6sh2ey19。 。

您最好的选择可能是将列表封装在另一个对象中,并让该对象处理内部列表上的锁定和解锁操作 这样,您就可以使新对象的“Add”方法异步,并让调用对象继续正常运行。任何时候您从中读取数据时,您很可能仍然需要等待任何其他对象完成更新。

List<T> can only support multiple readers concurrently. If you are going to use multiple threads to add to the list, you'll need to lock the object first. There is really no way around this, because without a lock you can still have someone reading from the list while another thread updates it (or multiple objects trying to update it concurrently also).

http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Your best bet probably is to encapsulate the list in another object, and have that object handle the locking and unlocking actions on the internal list. That way you could make your new object's "Add" method asynchronous and let the calling objects go on their merry way. Any time you read from it though you'll most likely still have to wait on any other objects finishing their updates though.

眼波传意 2024-09-05 22:44:31

我唯一能想到的解释空条目的方法是并发访问。毕竟,List 不是线程安全的。

基本上就是这样。我们被特别告知它不是线程安全的,所以我们不应该对并发访问导致破坏契约的行为感到惊讶。

至于为什么会出现这个特定问题,我们只能推测,因为 List<> 的私有实现是私有的(我知道我们有 Reflector 和共享源 - 但原则上它是私有的)。假设该实现涉及一个数组和一个“最后填充的索引”。还假设“添加项目”如下所示:

  • 确保数组对于另一个项目来说足够大
  • 最后填充的索引 <- 最后填充的索引 + 1
  • 数组[最后填充的索引] = 传入项目

现在假设有两个线程调用 Add。如果交错的操作序列最终如下所示:

  • 线程 A : 最后填充的索引 <- 最后填充的索引 + 1
  • 线程 B : 最后填充的索引 <- 最后填充的索引 + 1
  • 线程 A : array[最后填充的索引] = 输入item
  • Thread B : array[last populated index] =传入项

那么不仅数组中会有一个 null ,而且线程 A 尝试添加的项也不会出现在数组中根本!

现在,我不知道 List 内部是如何完成其​​工作的。我有一半的记忆是它是一个 ArrayList,它内部使用了这个方案;但事实上这并不重要。我怀疑任何期望非并发运行的列表机制都可以通过并发访问和足够“不幸”的操作交错来破坏。如果我们想要一个不提供线程安全性的 API 提供线程安全性,我们必须自己做一些工作 - 或者至少,如果 API 有时在我们不提供的情况下破坏它,我们不应该感到惊讶't。

对于您的要求

我不希望调用线程阻塞等待添加其项目

我的第一个想法是多生产者单消费者队列,其中想要添加项目的线程是生产者,它将项目异步分派到队列,并且有是一个单一的消费者,它将项目从队列中取出并通过适当的锁定将它们添加到列表中。我的第二个想法是,这感觉似乎比这种情况所需要的更严重,所以我会考虑一下。

The only thing I can think of to account for the null entries is the concurrent accesses. List<> isn't thread-safe, after all.

That's basically it. We are specifically told it's not thread-safe, so we shouldn't be surprised that concurrent access results in contract-breaking behaviour.

As to why this specific problem occurs, we can but speculate, since List<>'s private implementation is, well, private (I know we have Reflector and Shared Source - but in principle it is private). Suppose the implementation involves an array and a 'last populated index'. Suppose also that 'Add an item' looks like this:

  • Ensure the array is big enough for another item
  • last populated index <- last populated index + 1
  • array[last populated index] = incoming item

Now suppose there are two threads calling Add. If the interleaved sequence of operations ends up like this:

  • Thread A : last populated index <- last populated index + 1
  • Thread B : last populated index <- last populated index + 1
  • Thread A : array[last populated index] = incoming item
  • Thread B : array[last populated index] = incoming item

then not only will there be a null in the array, but also the item that thread A was trying to add won't be in the array at all!

Now, I don't know for sure how List<> does its stuff internally. I have half a memory that it is with an ArrayList, which internally uses this scheme; but in fact it doesn't matter. I suspect that any list mechanism that expects to be run non-concurrently can be made to break with concurrent access and a sufficiently 'unlucky' interleaving of operations. If we want thread-safety from an API that doesn't provide it, we have to do some work ourselves - or at least, we shouldn't be surprised if the API sometimes breaks its when we don't.

For your requirement of

I don't want the calling threads to block waiting to add their item

my first thought is a Multiple-Producer-Single-Consumer queue, wherein the threads wanting to add items are the producers, which dispatch items to the queue async, and there is a single consumer which takes items off the queue and adds them to the list with appropriate locking. My second thought is that this feels as if it would be heavier than this situation warrants, so I'll let it mull for a bit.

故人的歌 2024-09-05 22:44:31

如果您使用的是 .NET Framework 4,您可以查看新的 并发集合。当谈到线程时,最好不要自作聪明,因为很容易出错。同步会影响性能,但线程错误也会导致奇怪的、罕见的错误,这些错误很难追踪。

如果您仍在该项目中使用 Framework 2 或 3.5,我建议您将对列表的调用简单地包装在锁定语句中。如果您担心 Add 的性能(您是否在其他地方使用列表执行某些长时间运行的操作?),那么您始终可以在锁内创建列表的副本,并将该副本用于锁之外的长时间运行的操作。锁。简单地阻塞添加本身不应该成为性能问题,除非您有大量线程。如果是这种情况,您可以尝试 AakashM 推荐的多生产者单消费者队列。

If you're using .NET Framework 4, you might check out the new Concurrent Collections. When it comes to threading, it's better not to try to be clever, as it's extremely easy to get it wrong. Synchronization can impact performance, but the effects of getting threading wrong can also result in strange, infrequent errors that are a royal pain to track down.

If you're still using Framework 2 or 3.5 for this project, I recommend simply wrapping your calls to the list in a lock statement. If you're concerned about performance of Add (are you performing some long-running operation using the list somewhere else?) then you can always make a copy of the list within a lock and use that copy for your long-running operation outside the lock. Simply blocking on the Adds themselves shouldn't be a performance issue, unless you have a very large number of threads. If that's the case, you can try the Multiple-Producer-Single-Consumer queue that AakashM recommended.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文