Skip to content

Instantly share code, notes, and snippets.

@shanecelis
Created April 20, 2020 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shanecelis/9618585619f23ab95fe1da7613da0e3e to your computer and use it in GitHub Desktop.
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
// 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