///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // File: ThreadLocal.cs // Date: v1.0 - 5th April 2010 // v1.1 - 28th October 2010 - Added ThreadLocalManager // Author: Gavin Pugh // Details: A cross-platform 'Thread-local storage' implementation, which works under the 360 Compact Framework. // // Copyright (c) Gavin Pugh 2010 - Released under the zlib license: http://www.opensource.org/licenses/zlib-license.php ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; namespace Core { //! Standard interface for thread-local classes, to implement nicer cleanup functionality. public interface IThreadLocal { void FreeData( Thread thread_id ); } //! Thread-local storage manager. Handles easy cleanup of allocated TLS data for threads being destructed. public class ThreadLocalManager { //! No equivalent to C++ 'set' in C#, so I'll make the value type an empty class private class EmptyClass { } //! Store references to each threadlocal that has been accessed private static Dictionary m_tls_data = new Dictionary(); //! Attempt to register a threadlocal, we may have it already which means this does nothing. public static void Register( IThreadLocal thread_local ) { lock ( m_tls_data ) { if ( m_tls_data.ContainsKey( thread_local ) == false ) { m_tls_data.Add( thread_local, null ); } } } //! Free all TLS data associated with the current thread public static void FreeData() { FreeData( Thread.CurrentThread ); } //! Free all TLS data associated with the given specific thread public static void FreeData( Thread thread_id ) { lock ( m_tls_data ) { foreach ( IThreadLocal threadLocal in m_tls_data.Keys ) { threadLocal.FreeData( thread_id ); } } } } //! Thread-local class implementation for reference types public class ThreadLocal : IThreadLocal where T : class, new() { //! Thread entry struct. Used to implement an associative array container. private struct ThreadEntry { public Thread m_thread_id; public T m_data; } private const int MAX_THREADS = 16; //!< Maximum threads we'll support. This can be increased at cost to performance. private ThreadEntry[] m_data = new ThreadEntry[MAX_THREADS]; //!< Associative array of thread-specific data //! Implicit cast operator. Slow on 360 due to use of 'Thread.CurrentThread'. public static implicit operator T( ThreadLocal thread_local ) { return thread_local.GetData( Thread.CurrentThread ); } //! Data accessor. Slow on 360 due to use of 'Thread.CurrentThread'. public T Data { get { return GetData( Thread.CurrentThread ); } } //! Data accessor. Pass in a specific thread, saves expensive call to 'Thread.CurrentThread' on 360. public T GetData( Thread thread_id ) { for ( int i = 0; i < MAX_THREADS; i++ ) { if ( m_data[i].m_thread_id == thread_id ) { return m_data[i].m_data; } } // NOTE: Please only use your current thread. TryNewEntry() could potentially add two new entries to the // TLS array for the same thread. This would happen if two different threads passed the same Thread-id at // the same time, whilst there was currently no TLS data for that thread. #if DEBUG if ( thread_id != Thread.CurrentThread ) { throw new Exception( "Threads should not access data owned by other threads" ); } #endif //DEBUG return TryNewEntry( thread_id ); } //! Free data owned by the current thread. Does nothing if we own no data from that thread. public void FreeData() { FreeData( Thread.CurrentThread ); } //! Free data owned by a specific thread. Does nothing if we own no data from that thread. public void FreeData( Thread thread_id ) { lock ( this ) { for ( int i = 0; i < MAX_THREADS; i++ ) { if ( m_data[i].m_thread_id == thread_id ) { m_data[i] = default( ThreadEntry ); break; } } } } //! Helper function to add a new entry to the thread-entry array private T TryNewEntry( Thread thread_id ) { lock ( this ) { T new_data = new T(); for ( int i = 0; i < MAX_THREADS; i++ ) { // Empty slot? Let's use it if ( m_data[i].m_data == null ) { m_data[i].m_data = new_data; m_data[i].m_thread_id = thread_id; ThreadLocalManager.Register( this ); return new_data; } } throw new Exception( "MAX_THREADS limit hit! Please ensure you call FreeData()." ); } } } }