Is iterating over an array with a for loop a thread safe operation in C# ? What about iterating an IEnumerable<T> with a foreach loop? Is iterating over an array with a for loop a thread safe operation in C# ? What about iterating an IEnumerable<T> with a foreach loop? arrays arrays

Is iterating over an array with a for loop a thread safe operation in C# ? What about iterating an IEnumerable<T> with a foreach loop?


Is iterating over an array with a for loop a thread safe operation in C# ?

If you're strictly talking about reading from multiple threads, that will be thread safe for Array and List<T> and just about every collection written by Microsoft, regardless of if you're using a for or foreach loop. Especially in the example you have:

var temp = new List<int>();foreach (var name in Names){  temp.Add(name.Length * 2);}

You can do that across as many threads as you want. They'll all read the same values from Names happily.

If you write to it from another thread (this wasn't your question, but it's worth noting)

Iterating over an Array or List<T> with a for loop, it'll just keep reading, and it'll happily read the changed values as you come across them.

Iterating with a foreach loop, then it depends on the implementation. If a value in an Array changes part way through a foreach loop, it will just keep enumerating and give you the changed values.

With List<T>, it depends what you consider "thread safe". If you are more concerned with reading accurate data, then it kind of is "safe" since it will throw an exception mid-enumeration and tell you that the collection changed. But if you consider throwing an exception to be not safe, then it's not safe.

But it's worth noting that this is a design decision in List<T>, there is code that explicitly looks for changes and throws an exception. Design decisions brings us to the next point:

Can we assume that every collection that implements IEnumerable is safe to read across multiple threads?

In most cases it will be, but thread-safe reading is not guaranteed. The reason is because every IEnumerable requires an implementation of IEnumerator, which decides how to traverse the items in the collection. And just like any class, you can do anything you want in there, including non-thread-safe things like:

  • Using static variables
  • Using a shared cache for reading values
  • Not making any effort to handle cases where the collection changes mid-enumeration
  • etc.

You could even do something weird like make GetEnumerator() return the same instance of your enumerator every time its called. That could really make for some unpredictable results.

I consider something to not be thread safe if it can result in unpredictable results. Any of those things could cause unpredictable results.

You can see the source code for the Enumerator that List<T> uses, so you can see that it doesn't do any of that weird stuff, which tells you that enumerating List<T> from multiple threads is safe.


To assert that your code is thread-safe means that we must take your words for granted that there is no code inside the UselessService that will try to replace concurrently the contents of the Names array with something like "tom" and "jerry" or (more sinister) null and null. On the other hand using an ImmutableArray<string> would guarantee that the code is thread-safe, and everybody could be assured about that just by looking the type of the static readonly field, without having to inspect carefully the rest of the code.

You may find interesting these comments from the source code of the ImmutableArray<T>, regarding some implementation details of this struct:

A readonly array with O(1) indexable lookup time.

This type has a documented contract of being exactly one reference-type field in size. Our own System.Collections.Immutable.ImmutableInterlocked class depends on it, as well as others externally.

IMPORTANT NOTICE FOR MAINTAINERS AND REVIEWERS:

This type should be thread-safe. As a struct, it cannot protect its own fields from being changed from one thread while its members are executing on other threads because structs can change in place simply by reassigning the field containing this struct. Therefore it is extremely important that Every member should only dereference this ONCE. If a member needs to reference the array field, that counts as a dereference of this. Calling other instance members (properties or methods) also counts as dereferencing this. Any member that needs to use this more than once must instead assign this to a local variable and use that for the rest of the code instead. This effectively copies the one field in the struct to a local variable so that it is insulated from other threads.