mirror of
https://github.com/ICDSystems/ICD.Common.Utils.git
synced 2026-02-17 13:45:05 +00:00
feat: Invoke threads are tracked internally and can be printed to a table
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user