Created
April 20, 2020 09:55
-
-
Save shanecelis/9618585619f23ab95fe1da7613da0e3e to your computer and use it in GitHub Desktop.
Provides a thread-safe global or thread-local context to store a value
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Java Genetic Algorithm Library. | |
// Copyright (c) 2017 Franz Wilhelmstötter | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
// | |
// Author: | |
// Franz Wilhelmstötter (franz.wilhelmstoetter@gmx.at) | |
using System; | |
using System.Threading; | |
/** Set up a thread-safe global or thread-local context to store a value that | |
may be temporarily superseded. | |
This is almost like having dynamically scoped variables. The constraint is | |
they're scoped by thread. | |
Example | |
------- | |
Suppose you have functions that require a Random class instance. A | |
RandomRegistry contains a static Context<Random> instance, so the random | |
instance can be acquired without having to pass it everywhere. One | |
constraint is we don't want to interfere with another thread's random | |
because Random isn't thread-safe and because we might want to be able to | |
reproduce the same random stream given the same seed. | |
``` | |
var oldRandom = context.LocalValue; | |
try { | |
context.LocalValue = new Random(seed); | |
obj.MethodThatCallsSomethingThatUsesRandomFromContext(); | |
} finally { | |
context.LocalValue = oldRandom; | |
} | |
``` | |
Or the With() method returns an IDisposable object that's convenient with | |
`using()`. | |
``` | |
using (RandomRegistry.context.With(new Random(seed))) { | |
obj.MethodThatCallsSomethingThatUsesRandomFromContext(); | |
} | |
``` | |
Imagine deeper within the library something calls this `list.Random()` extension | |
method. | |
``` | |
public static T Random<T>(this IList<T>) { | |
switch (list.Count) { | |
case 0: | |
throw new Exception("List must contain something to pick a random item."); | |
case 1: | |
return list[0]; | |
default: | |
return list[RandomRegistry.context.Value.Next(list.Count)]; | |
} | |
} | |
``` | |
Modifications | |
------------- | |
I, Shane Celis, have added the `With()` method and changed the getters and | |
setters to properties and exposed thread-local values via the `LocalValue` | |
property. | |
Acknowledgments | |
--------------- | |
This was originally written[1] by Franz Wilhelmstötter | |
(franz.wilhelmstoetter@gmx.at) for the Jenetics library, a genetic algorithm | |
library for Java, and licensed under the Apache License, Version 2.0. It was | |
ported[2] to C# by Rupert Meindl. | |
[1]: https://github.com/jenetics/jenetics/blob/cc59dffcddf55c6960f3459309dc7cc360970cdd/jenetics/src/main/java/io/jenetics/util/Context.java | |
[2]: https://github.com/rmeindl/jenetics.net/blob/7317b9bd7a07c6826880ab9f9ab5b8f45fcbf9bf/src/core/Jenetics/Util/Context.cs | |
*/ | |
public class Context<T> { | |
private readonly T _default; | |
private readonly ThreadLocal<Entry> _threadLocalEntry = new ThreadLocal<Entry>(); | |
private Entry _entry; | |
/** Set the global value to the defaultValue. */ | |
public Context(T defaultValue) { | |
_default = defaultValue; | |
_entry = new Entry(defaultValue); | |
} | |
/** Get or set the current value. | |
If no thread-local value has been set with `context.LocalValue = x` or | |
`context.With(x)` then it will return the global in a thread-safe fashion; | |
otherwise, it will return the current thread-local value. | |
*/ | |
public T Value { | |
get { | |
var e = _threadLocalEntry.Value; | |
return (e != null ? e : Volatile.Read(ref _entry)).Value; | |
} | |
set { | |
var e = _threadLocalEntry.Value; | |
// Why null instead of e.IsValueCreated? | |
if (e != null) | |
e.Value = value; | |
else | |
Volatile.Write(ref _entry, new Entry(value)); | |
} | |
} | |
public bool HasLocalValue => _threadLocalEntry.IsValueCreated; | |
/** Get or set the current thread-local value. */ | |
public T LocalValue { | |
get { | |
var e = _threadLocalEntry.Value; | |
return e != null ? e.Value : default(T); | |
} | |
set { | |
var e = _threadLocalEntry.Value; | |
if (e != null) | |
_threadLocalEntry.Value = e.Inner(value); | |
else | |
_threadLocalEntry.Value = new Entry(value, Thread.CurrentThread); | |
} | |
} | |
/** Lose the last locally set value. */ | |
public void LocalPop() { | |
if (_threadLocalEntry != null && _threadLocalEntry.Value != null) | |
_threadLocalEntry.Value = _threadLocalEntry.Value.Parent; | |
} | |
/** Reset to the default value. */ | |
public void Reset() { | |
Value = _default; | |
} | |
/** | |
Setup the context with a new value. | |
Example: Suppose you have functions that require a Random instance. You | |
don't want to interfere with another thread's random though. One could do | |
something like this. | |
using (RandomRegistry.context.With(new Random(seed))) { | |
obj.MethodThatCallsSomethingThatUsesRandomFromContext(); | |
} | |
*/ | |
public IDisposable With(T value) { | |
var e = _threadLocalEntry.Value; | |
if (e != null) | |
_threadLocalEntry.Value = e.Inner(value); | |
else | |
_threadLocalEntry.Value = new Entry(value, Thread.CurrentThread); | |
return new ADisposable(() => { | |
if (_threadLocalEntry != null && _threadLocalEntry.Value != null) | |
_threadLocalEntry.Value = _threadLocalEntry.Value.Parent; | |
}); | |
} | |
/** A convenience IDisposable for resetting something via an action. */ | |
private class ADisposable : IDisposable { | |
bool disposed = false; | |
Action action; | |
public ADisposable(Action action) { | |
this.action = action; | |
} | |
public void Dispose() { | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
private void Dispose(bool disposing) { | |
if (disposed) | |
return; | |
if (disposing) { | |
// Free managed objects here. | |
action(); | |
} | |
// Free unmanaged objects here. | |
disposed = true; | |
} | |
~ADisposable() { | |
Dispose(false); | |
} | |
} | |
private class Entry { | |
private readonly Thread _thread; | |
internal readonly Entry Parent; | |
internal T Value; | |
private Entry(T value, Entry parent, Thread thread) { | |
Value = value; | |
Parent = parent; | |
_thread = thread; | |
} | |
public Entry(T value, Thread thread) : this(value, null, thread) { } | |
public Entry(T value) : this(value, null, null) { } | |
public Entry Inner(T value) { | |
System.Diagnostics.Debug.Assert(_thread == Thread.CurrentThread, | |
"Created inner Entry on unexpected thread."); | |
return new Entry(value, this, _thread); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment