I have a class that exposes a fluent interface style that I also want to be thread safe.
At the moment, calling chainable methods on an instance of the class sets up various collections with operations (Func<T>
's).
When the result is requested the real work happens. This allows for users to chain the method calls in any order such that:
var result = myFluentThing
.Execute(() => serviceCall.ExecHttp(), 5)
.IfExecFails(() => DoSomeShizzle())
.Result<TheResultType>();
(Here, 5 is the number of times to re-try a failied service call.)
Obviously this isn't threadsafe or reentrant.
What are some common design patterns that solve this problem?
If the Execute method had to be called first I could simply returned a new instance of the class to work with each time however since any method can be called at any point in the chain, how would you solve this problem?
I'm more interested in understanding various ways to solve this rather than a single answer just to "get it working right".
I've put the full code on GitHub incase anyone needs a wider context on what I'm aiming to achieve: https://github.com/JamieDixon/ServiceManager
Fluent Ribbon User Interface The Fluent interface makes it easier to find powerful features by replacing menus and toolbars with a Ribbon that organizes and presents capabilities in a way that corresponds more directly to how people work.
Fluent interface, first coined as a term by Martin Fowler, is a very convenient way of communicating with objects in OOP. It makes their facades easier to use and understand. However, it ruins their internal design, making them more difficult to maintain.
A fluent interface is an object-oriented API that depends largely on method chaining. The goal of a fluent interface is to reduce code complexity, make the code readable, and create a domain specific language (DSL). It is a type of method chaining in which the context is maintained using a chain.
A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language.
We can split fluent approaches into two types; mutating and non-mutating.
Mutating cases aren't very common in .NET (the fluent apporach wasn't generally until Linq introduced it's very heavy use of the fluent approach, Java in comparison uses them heavily in property setters where C# instead uses properties to give the same syntax for setting a property as setting a field). One example though is StringBuilder
.
StringBuilder sb = new StringBuilder("a").Append("b").Append("c");
The basic form is:
TypeOfContainingClass SomeMethod(/*... arguments ... */)
{
//Do something, generally mutating the current object
//though we could perhaps mix in some non-mutating methods
//with the mutating methods a class like this uses, for
//consistency.
return this;
}
This is an inherently non-threadsafe approach, because it mutates the object in question, so two calls from different threads will interfere. It's certainly possible to create a class that will be thread-safe in the face of such calls in the sense that it won't be put into an incoherent state, but generally when we take this approach we care about the results of those mutations, and those alone. E.g. with the StringbBuilder
example above, we care that sb
ends up holding the string "abc"
, a thread-safe StringBuilder
would be pointless, because we wouldn't consider a guarantee that it would successfully end up either holding "abc"
or "acb"
to be acceptable - such a hypothetical class itself would be threadsafe, but the calling code would not.
(This does not mean that we can't use such classes in thread-safe code; we can use any classes in thread-safe code, but it doesn't help us).
Now, the non-mutating form is thread-safe, in and of itself. This doesn't mean all uses are thread-safe, but it means they can be. Consider the following LINQ code:
var results = someSource
.Where(somePredicate)
.OrderBy(someOrderer)
.Select(someFactory);
This is thread-safe as long as:
This may seem like a lot of of criteria, but actually, the last are all the same criteria: We require our Func
instances to be functional - they don't have side-effects*, but rather they return a result that depends upon their input (we can bend some rules about being functional while still being thread-safe, but let's not complicate things right now). And well, that's presumably the sort of case they were thinking of when they came up with the name Func
. Note that the most common sort of case with Linq fits this description. E.g.:
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, with 99% of property implementations, calling the getter
s from multiple threads is thread-safe as long as we don't have another thread writing. This is a common scenario, so we're thread-safe in terms of being able to cater for that case safely, though we are not thread-safe in the face of another thread doing such mutations.
Similarly, we can split sources like someSource
into four categories:
The vast majority of the first case is thread-safe in the face solely of other readers. Some are thread-safe in the face of concurrent writers too. With the second case it depends on the implementation - does it obtain a connection etc. as needed in the current thread, or use one shared between calls? With the third case, it's definitely not thread-safe unless we consider "losing" those items that another thread got instead of us to be acceptable. And well, "Other" is as "Other" does.
So, from all that, we don't have something that guarantees thread-safety, but we do have something that gives us a sufficient degree of thread-safety that if used with other components providing the degree of thread-safety we need, we get it.
100% thread-safety in the face of all possible uses? No, nothing gives you that. Really, no datatype is thread-safe, only particular groups of operations - in describing a datatype as "thread-safe" we are saying all it's member methods and properties are treadsafe, and in turn in describing a method or property as threadsafe we are saying that it in itself is threadsafe and therefore can be part of a threadsafe group of operations, but not every group of thread-safe operations is thread-safe.
If we want to implement this kind of approach we need to create a method or extension that creates an object, based on the object called on (if a member rather than an extension) and the parameters, but does not mutate.
Let's have two separate implementations of a method like Enumerable.Select
for discussion:
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 in some way based on the contents of source
. Only the second though has the sort of delayed iteration of source
that we get from linq. The first actually lets us deal with some multi-threading situations better than the second, but does so nastily (if you want to e.g. get a copy while holding a lock as part of your concurrency management, do it by getting a copy while holding a lock, not in the midst of anything else).
In either case, it is the object returned that is the key to what thread-safety we can offer. The first has obtained all information about its results, so as long as it is only referenced locally to a single thread it's thread-safe. The second has the information needed to produce those results, so as long it is only referenced locally to a single thread, accessing the source is thread-safe, and invoking the Func
is thread-safe, it's thread-safe (and those also applied to creating the first one in the first place).
In summary then, if we have methods that produce objects which refer solely to the source and the Func
s, we can be as thread-safe as the source and Func
s are, but no safer.
*Memoisation results in a side-effect that isn't visible from the outside, as an optimisation. Should it be used by our Func
s or by something they call into (e.g. a getter), then the memoisation would have to be implemented in a thread-safe manner for thread-safety to be possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With