From e437a9442c86b85c39ba1c13f49b2281b0543796 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Mon, 13 Aug 2018 15:00:15 -0400 Subject: [PATCH] feat: Invoke threads are tracked internally and can be printed to a table --- ICD.Common.Utils/ThreadingUtils.cs | 135 +++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 6 deletions(-) diff --git a/ICD.Common.Utils/ThreadingUtils.cs b/ICD.Common.Utils/ThreadingUtils.cs index f719e21..44ab329 100644 --- a/ICD.Common.Utils/ThreadingUtils.cs +++ b/ICD.Common.Utils/ThreadingUtils.cs @@ -1,17 +1,35 @@ using System; +using System.Collections.Generic; +using System.Linq; using ICD.Common.Properties; +using ICD.Common.Utils.Collections; +using ICD.Common.Utils.Extensions; using ICD.Common.Utils.Services; using ICD.Common.Utils.Services.Logging; #if SIMPLSHARP using Crestron.SimplSharp; +using Crestron.SimplSharp.Reflection; #else using System.Threading.Tasks; +using System.Reflection; #endif namespace ICD.Common.Utils { public static class ThreadingUtils { + private static readonly IcdHashSet s_Threads; + private static readonly SafeCriticalSection s_ThreadsSection; + + /// + /// Static contstructor. + /// + static ThreadingUtils() + { + s_Threads = new IcdHashSet(); + s_ThreadsSection = new SafeCriticalSection(); + } + /// /// Wait until the given condition is true. /// @@ -52,9 +70,12 @@ namespace ICD.Common.Utils /// /// [PublicAPI] - public static object SafeInvoke(Action callback) + public static void SafeInvoke(Action callback) { - return SafeInvoke(unused => callback(), null); + if (callback == null) + throw new ArgumentNullException("callback"); + + SafeInvoke(callback.GetMethodInfo(), unused => callback(), null); } /// @@ -64,12 +85,41 @@ namespace ICD.Common.Utils /// /// [PublicAPI] - public static object SafeInvoke(Action callback, T param) + public static void SafeInvoke(Action callback, T param) { + if (callback == null) + throw new ArgumentNullException("callback"); + + SafeInvoke(callback.GetMethodInfo(), callback, param); + } + + /// + /// Executes the callback as a short-lived, threaded task. + /// + /// + /// + /// + /// + [PublicAPI] + private static void SafeInvoke(MethodInfo methodInfo, Action callback, T param) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + ThreadState state = new ThreadState + { + MethodInfo = methodInfo, + }; + state.Callback = GetHandledCallback(state, callback, param); + + // Add the state here so the created thread doesn't get garbage collected + AddThreadState(state); + + state.Handle = #if SIMPLSHARP - return CrestronInvoke.BeginInvoke(unused => GetHandledCallback(callback, param)(), null); + CrestronInvoke.BeginInvoke(unused => state.Callback(), null); #else - return Task.Run(GetHandledCallback(callback, param)); + Task.Run(state.Callback); #endif } @@ -78,15 +128,19 @@ namespace ICD.Common.Utils /// http://www.crestronlabs.com/showthread.php?12205-Exception-in-CrestronInvoke-thread-crashes-the-program /// /// + /// /// /// - private static Action GetHandledCallback(Action callback, T param) + private static Action GetHandledCallback(ThreadState state, Action callback, T param) { return () => { try { + state.Started = IcdEnvironment.GetLocalTime(); + callback(param); + RemoveThreadState(state); } catch (Exception e) { @@ -95,5 +149,74 @@ namespace ICD.Common.Utils } }; } + + private static IEnumerable GetActiveThreads() + { + return s_ThreadsSection.Execute(() => s_Threads.ToArray(s_Threads.Count)); + } + + private static void AddThreadState(ThreadState state) + { + s_ThreadsSection.Execute(() => s_Threads.Add(state)); + } + + private static void RemoveThreadState(ThreadState state) + { + s_ThreadsSection.Execute(() => s_Threads.Remove(state)); + } + + public static string PrintThreads() + { + TableBuilder builder = new TableBuilder("Name", "Started", "Duration"); + + IEnumerable states = GetActiveThreads().OrderBy(s => s.Name) + .ThenBy(s => s.Started); + + foreach (ThreadState state in states) + builder.AddRow(state.Name, state.Started, state.Duration); + + return builder.ToString(); + } + + private sealed class ThreadState + { + private string m_CachedName; + + /// + /// Gets the time the thread was instantiated. + /// + public DateTime? Started { get; set; } + + /// + /// Gets the duration of the thread. + /// + public TimeSpan? Duration { get { return IcdEnvironment.GetLocalTime() - Started; } } + + /// + /// Threads can be garbage collected before they execute so we keep a reference. + /// + [UsedImplicitly] + public object Handle { get; set; } + + /// + /// Human readable name for the thread. + /// + public string Name { get { return m_CachedName = m_CachedName ?? BuildName(); } } + + /// + /// The action that is being performed by the thread. + /// + public Action Callback { get; set; } + + /// + /// Used for providing debug information on the executing method. + /// + public MethodInfo MethodInfo { get; set; } + + private string BuildName() + { + return string.Format("{0}.{1}", MethodInfo.DeclaringType.Name, MethodInfo.GetSignature(true)); + } + } } }