We can divide fluent approaches into two types; mutating and not mutating.
Mutation cases are not very common in .NET (a free application wasn’t, as a rule, until Linq introduced it into a very intensive use of the free approach, Java comparison uses them to a large extent in property settings, where C # instead uses properties to give the same syntax for setting a property as setting a field). One example is StringBuilder .
StringBuilder sb = new StringBuilder("a").Append("b").Append("c");
Basic form:
TypeOfContainingClass SomeMethod() {
This is essentially an unsafe approach, because it mutates the object in question, so two calls from different threads will interact. Of course, you can create a class that will be thread safe in the face of such challenges in the sense that it will not be transferred to an incoherent state, but, as a rule, when we use this approach, we care about the results of these mutations, and only those. For instance. with the StringbBuilder example StringbBuilder , we make sure that sb terminates the string "abc" , the thread-safe StringBuilder would be pointless because we will not take the guarantee that it will successfully complete "abc" or "acb" to be acceptable - such a hypothetical class in itself would be thread safe, but there would be no call code.
(This does not mean that we cannot use such classes in thread-safe code, we can use any classes in thread-safe code, but this does not help us).
Now the non-mutating form is thread safe in itself. This does not mean that all uses are thread safe, but it does mean that they can be. Consider the following LINQ code:
var results = someSource .Where(somePredicate) .OrderBy(someOrderer) .Select(someFactory);
This is thread safe if:
- Iterating through someSource is thread safe.
- Calling somePredicate is thread safe.
- Calling someOrder is thread safe.
- Calling someFactory is thread safe.
This may seem like a lot of criteria, but in fact the latter are the same criteria: we require our Func instances to be functional - they have no side effects *, but they return a result that depends on their input (we can bend some rules about being functional, but still being thread safe, but we will not complicate the situation right now). And well, this is probably the case they were thinking of when they came up with the name Func . Please note that the most common type of case with Linq matches this description. For instance:.
var results = someSource .Where(item => item.IsActive)//functional. Thread-safe as long as accessing IsActive is. .OrderBy(item => item.Priority)//functional. Thread-safe as long as accessing Priority is. .Select(item => new {item.ID, item.Name});//functional. Thread-safe as long as accessing ID and Name is.
Now that 99% of the property implementations, calling getter from multiple threads is thread safe unless we have another thread record. This is a common scenario, so we are thread safe from the point of view that we can safely handle this case, although we are not thread safe in the face of another thread performing such mutations.
Similarly, we can divide sources, such as someSource , into four categories:
- Collection in mind.
- A call against a database or other data source.
- An enumerable one that will perform one pass through information obtained somewhere, but where the source does not have the information necessary to retrieve this information at the second iteration.
- Others.
The vast majority of the first case is thread safe only by the face of other readers. Some of them are thread safe in the face of simultaneous authors. In the second case, it depends on the implementation: does it get the connection, etc. As needed in the current thread or uses one of the shared between calls? In the third case, it is definitely not thread safe unless we consider the “loss” of those elements that the other thread received in our place to be acceptable. And well, "Other," like "Other," does.
So, of all this, we don’t have something that guarantees thread safety, but we have something that gives us a sufficient degree of protection against threads, if we use this with other components that provide a degree of thread safety to do.
100% thread safety with all possible use cases? No, nothing gives you this. Indeed, no data type is thread safe, only certain groups of operations — when describing a data type as “thread safe”, we say that all its member methods and properties are textual and, in turn, describe a method or property as thread safe, we saying that it itself is thread safe and therefore can be part of a thread-safe group of operations, but not every thread-safe group of operations is thread safe.
If we want to implement this approach, we need to create a method or extension that creates an object based on the object it is called on (if the member, not the extension), and parameters, but does not mutate.
Let's look at two separate implementations of a method of type Enumerable.Select :
public static IEnumerable<TResult> SelectRightNow<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector) { var list = new List<TResult>(); foreach(TSource item in source) list.Add(selector(item)); return list; } public static IEnumerable<TResult> SelectEventually<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector) { foreach(TSource item in source) yield return selector(item); }
In both cases, the method immediately returns a new object that is somehow based on the contents of source . Only the second, although it has a kind of delayed iteration of source , which we get from linq. The first one actually allows us to deal with some multi-threaded situations better than the second one, but does it vaguely (if you want, for example, getting a copy while holding the lock as part of your concurrency control, do this by getting a copy while holding the lock, and not in the middle of everything else).
In any case, this is a returned object, which is the key to what thread safety we can offer. The first one got all the information about its results, therefore, if it only locally refers to one thread, it is thread-safe. The second has the information necessary to obtain these results, therefore, while it is tied to only one thread, access to the source is thread-safe, and the Func call is thread-safe, it is thread-safe (and, therefore, it is also used to create the first one )
So, if we have methods that produce objects that relate exclusively to the source and Func s, we can be thread safe because the source and Func are, but not more secure.
* Remembering leads to a side effect that is not visible from the outside, like optimization. If it is used by our Func or something that they are accessing (for example, getter), then the memo should be implemented in thread-safe mode to ensure thread safety.