feat: Invoke threads are tracked internally and can be printed to a table

This commit is contained in:
Chris Cameron
2018-08-13 15:00:15 -04:00
parent 478261e888
commit e437a9442c

View File

@@ -1,17 +1,35 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using ICD.Common.Properties; using ICD.Common.Properties;
using ICD.Common.Utils.Collections;
using ICD.Common.Utils.Extensions;
using ICD.Common.Utils.Services; using ICD.Common.Utils.Services;
using ICD.Common.Utils.Services.Logging; using ICD.Common.Utils.Services.Logging;
#if SIMPLSHARP #if SIMPLSHARP
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
#else #else
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Reflection;
#endif #endif
namespace ICD.Common.Utils namespace ICD.Common.Utils
{ {
public static class ThreadingUtils public static class ThreadingUtils
{ {
private static readonly IcdHashSet<ThreadState> s_Threads;
private static readonly SafeCriticalSection s_ThreadsSection;
/// <summary>
/// Static contstructor.
/// </summary>
static ThreadingUtils()
{
s_Threads = new IcdHashSet<ThreadState>();
s_ThreadsSection = new SafeCriticalSection();
}
/// <summary> /// <summary>
/// Wait until the given condition is true. /// Wait until the given condition is true.
/// </summary> /// </summary>
@@ -52,9 +70,12 @@ namespace ICD.Common.Utils
/// </summary> /// </summary>
/// <param name="callback"></param> /// <param name="callback"></param>
[PublicAPI] [PublicAPI]
public static object SafeInvoke(Action callback) public static void SafeInvoke(Action callback)
{ {
return SafeInvoke<object>(unused => callback(), null); if (callback == null)
throw new ArgumentNullException("callback");
SafeInvoke<object>(callback.GetMethodInfo(), unused => callback(), null);
} }
/// <summary> /// <summary>
@@ -64,12 +85,41 @@ namespace ICD.Common.Utils
/// <param name="callback"></param> /// <param name="callback"></param>
/// <param name="param"></param> /// <param name="param"></param>
[PublicAPI] [PublicAPI]
public static object SafeInvoke<T>(Action<T> callback, T param) public static void SafeInvoke<T>(Action<T> callback, T param)
{ {
if (callback == null)
throw new ArgumentNullException("callback");
SafeInvoke(callback.GetMethodInfo(), callback, param);
}
/// <summary>
/// Executes the callback as a short-lived, threaded task.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="methodInfo"></param>
/// <param name="callback"></param>
/// <param name="param"></param>
[PublicAPI]
private static void SafeInvoke<T>(MethodInfo methodInfo, Action<T> 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 #if SIMPLSHARP
return CrestronInvoke.BeginInvoke(unused => GetHandledCallback(callback, param)(), null); CrestronInvoke.BeginInvoke(unused => state.Callback(), null);
#else #else
return Task.Run(GetHandledCallback(callback, param)); Task.Run(state.Callback);
#endif #endif
} }
@@ -78,15 +128,19 @@ namespace ICD.Common.Utils
/// http://www.crestronlabs.com/showthread.php?12205-Exception-in-CrestronInvoke-thread-crashes-the-program /// http://www.crestronlabs.com/showthread.php?12205-Exception-in-CrestronInvoke-thread-crashes-the-program
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="state"></param>
/// <param name="callback"></param> /// <param name="callback"></param>
/// <param name="param"></param> /// <param name="param"></param>
private static Action GetHandledCallback<T>(Action<T> callback, T param) private static Action GetHandledCallback<T>(ThreadState state, Action<T> callback, T param)
{ {
return () => return () =>
{ {
try try
{ {
state.Started = IcdEnvironment.GetLocalTime();
callback(param); callback(param);
RemoveThreadState(state);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -95,5 +149,74 @@ namespace ICD.Common.Utils
} }
}; };
} }
private static IEnumerable<ThreadState> 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<ThreadState> 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;
/// <summary>
/// Gets the time the thread was instantiated.
/// </summary>
public DateTime? Started { get; set; }
/// <summary>
/// Gets the duration of the thread.
/// </summary>
public TimeSpan? Duration { get { return IcdEnvironment.GetLocalTime() - Started; } }
/// <summary>
/// Threads can be garbage collected before they execute so we keep a reference.
/// </summary>
[UsedImplicitly]
public object Handle { get; set; }
/// <summary>
/// Human readable name for the thread.
/// </summary>
public string Name { get { return m_CachedName = m_CachedName ?? BuildName(); } }
/// <summary>
/// The action that is being performed by the thread.
/// </summary>
public Action Callback { get; set; }
/// <summary>
/// Used for providing debug information on the executing method.
/// </summary>
public MethodInfo MethodInfo { get; set; }
private string BuildName()
{
return string.Format("{0}.{1}", MethodInfo.DeclaringType.Name, MethodInfo.GetSignature(true));
}
}
} }
} }