Created
June 5, 2021 16:07
-
-
Save rajibchy/760ac9c3264ae79d311524638f680789 to your computer and use it in GitHub Desktop.
Thread-safe Concurrent Process Manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <![CDATA[copyright]]> | |
| /// Copyright (c) 2018, Sow ( https://safeonline.world, https://www.facebook.com/safeonlineworld). (https://github.com/RKTUXYN) All rights reserved. | |
| /// Copyrights licensed under the New BSD License. | |
| /// See the accompanying LICENSE file for terms. | |
| /// <![CDATA[copyright]]> | |
| /// <![CDATA[Author]]> | |
| /// By Rajib Chy | |
| /// On 5/27/2021 7:56:04 PM | |
| /// <![CDATA[Author]]> | |
| using System; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Threading; | |
| using System.Diagnostics; | |
| using System.Globalization; | |
| namespace Sow.Framework { | |
| public abstract class ProcInfo { | |
| public string Index { get; set; } | |
| public string LibDirectory { get; set; } | |
| public string WorkingDirectory { get; set; } | |
| public bool ReadStandardOutput { get; set; } | |
| public string FileName { get; set; } | |
| public string Arguments { get; set; } | |
| } | |
| public interface IProcManager<TProcInfo> { | |
| Action<TProcInfo, string> OnData { get; set; } | |
| Action<TProcInfo, string> OnError { get; set; } | |
| Action<TProcInfo> OnExit { get; set; } | |
| Action<Exception> OnInternalError { get; set; } | |
| bool IsRunning( string index ); | |
| void KillProcess( string index ); | |
| void OpenProcess( TProcInfo procInfo ); | |
| decimal GetProcMemoryUsages( string index ); | |
| void Dispose( ); | |
| } | |
| public static class ProcHelp { | |
| public static void RunGC( ) { | |
| GC.Collect( ); | |
| GC.WaitForPendingFinalizers( ); | |
| } | |
| public static Process GetProc( int pid ) { | |
| if ( pid < 0 ) return null; | |
| Process[] processlist = Process.GetProcesses( ); | |
| return processlist.FirstOrDefault( pr => pr.Id == pid ); | |
| } | |
| public static decimal GetAppMemoryUsages( int id ) { | |
| Process proc = GetProc( id ); | |
| if ( proc == null ) return -1; | |
| return GetAppMemoryUsages( proc ); | |
| } | |
| /// <summary> | |
| /// If argument proc is null then return the current app memory allocation MB | |
| /// </summary> | |
| /// <param name="proc"></param> | |
| /// <returns>decimal</returns> | |
| public static decimal GetAppMemoryUsages( Process proc = null ) { | |
| if ( proc == null ) { | |
| proc = Process.GetCurrentProcess( ); | |
| } | |
| proc.Refresh( ); | |
| decimal memory = ( ( decimal )proc.WorkingSet64 ) / ( 1024 * 1024 ); | |
| proc.Dispose( ); | |
| return Math.Round( memory, 2 ); | |
| } | |
| public static string GetAppMemoryUsages( string procName ) { | |
| try { | |
| ProcessStartInfo ps = new ProcessStartInfo( "tasklist" ) { | |
| Arguments = "/fi \"IMAGENAME eq " + procName + ".*\" /FO CSV /NH", | |
| RedirectStandardOutput = true, | |
| CreateNoWindow = true, | |
| UseShellExecute = false | |
| }; | |
| using ( Process p = Process.Start( ps ) ) { | |
| if ( p.WaitForExit( 1000 ) ) { | |
| var s = p.StandardOutput.ReadToEnd( ).Split( '\"' ); | |
| return s[ 9 ].Replace( "\"", "" ); | |
| } | |
| } | |
| } catch { } | |
| return "Unable to get memory usage"; | |
| } | |
| public static void RestartApp(string arguments ) { | |
| // "D:\project\sow-trade-plus-hook\native\Workstation\TradeFix\bin\Release\TradeFix.exe" www.google.com | |
| if(string.IsNullOrEmpty( arguments ) ) { | |
| string[] args = Environment.CommandLine.Split( new char[] { ' ' } ); | |
| arguments = string.Empty; | |
| if ( args.Length > 0 ) { | |
| arguments = args[ 1 ]; | |
| } | |
| } | |
| using ( Process proc = new Process( ) ) { | |
| proc.StartInfo.FileName = System.Reflection.Assembly.GetEntryAssembly( ).Location; | |
| proc.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory( ); | |
| proc.StartInfo.Arguments = arguments; | |
| proc.StartInfo.CreateNoWindow = false; | |
| proc.StartInfo.ErrorDialog = false; | |
| proc.StartInfo.UseShellExecute = false; | |
| proc.Start( ); | |
| proc.WaitForExit( 100 ); | |
| } | |
| } | |
| } | |
| public class ProcManager<TProcInfo> : IProcManager<TProcInfo> where TProcInfo : ProcInfo { | |
| readonly ConcurrentStorage<string, int> _procs; | |
| readonly ConcurrentStorage<string, TProcInfo> _procInfos; | |
| readonly ConcurrentStorage<string, Thread> _threadProc; | |
| public Action<TProcInfo, string> OnData { get; set; } | |
| public Action<TProcInfo, string> OnError { get; set; } | |
| public Action<TProcInfo> OnExit { get; set; } | |
| public Action<Exception> OnInternalError { get; set; } | |
| public ProcManager( ) { | |
| _procs = new ConcurrentStorage<string, int>( ); | |
| _threadProc = new ConcurrentStorage<string, Thread>( ); | |
| _procInfos = new ConcurrentStorage<string, TProcInfo>( ); | |
| } | |
| public decimal GetProcMemoryUsages( string index ) { | |
| _procInfos.TryGet( index, out TProcInfo procInfo ); | |
| if ( procInfo == null ) return -1; | |
| decimal memory = -1; | |
| if ( _procs.TryGet( index, out int id ) ) { | |
| memory = ProcHelp.GetAppMemoryUsages( id ); | |
| } | |
| return memory; | |
| } | |
| private void UpdateProcInfo( TProcInfo procInfo ) { | |
| if ( string.IsNullOrEmpty( procInfo.LibDirectory ) ) { | |
| procInfo.LibDirectory = procInfo.WorkingDirectory; | |
| } | |
| if ( string.IsNullOrEmpty( procInfo.Index ) ) { | |
| procInfo.Index = Guid.NewGuid( ).ToString( "N" ); | |
| } | |
| } | |
| private void FireAndForget( TProcInfo procInfo ) { | |
| using ( Process proc = new Process( ) ) { | |
| proc.StartInfo.FileName = Path.Combine( procInfo.LibDirectory, procInfo.FileName ); | |
| proc.StartInfo.WorkingDirectory = procInfo.WorkingDirectory; | |
| proc.StartInfo.Arguments = procInfo.Arguments; | |
| proc.StartInfo.CreateNoWindow = false; | |
| proc.StartInfo.ErrorDialog = false; | |
| proc.StartInfo.UseShellExecute = false; | |
| proc.Start( ); | |
| _procs.TryAdd( procInfo.Index, proc.Id ); | |
| proc.WaitForExit( 1000 ); | |
| } | |
| } | |
| public void OpenProcess( TProcInfo procInfo ) { | |
| UpdateProcInfo( procInfo ); KillProcess( procInfo.Index ); | |
| _procInfos.TryAdd( procInfo.Index, procInfo ); | |
| if ( procInfo.ReadStandardOutput ) { | |
| ReadStandardOutput( procInfo ); | |
| } else { | |
| FireAndForget( procInfo ); | |
| } | |
| return; | |
| } | |
| public bool IsRunning( string index ) { | |
| _procInfos.TryGet( index, out TProcInfo procInfo ); | |
| if ( procInfo == null ) return false; | |
| _procInfos.TryRemove( index ); | |
| if ( !_procs.ContainsKey( index ) ) return false; | |
| if ( procInfo.ReadStandardOutput == true ) { | |
| if ( !_threadProc.ContainsKey( index ) ) return false; | |
| } | |
| if ( _procs.TryGet( index, out int id ) ) { | |
| if ( ProcHelp.GetProc( id ) != null ) return true; | |
| } | |
| return false; | |
| } | |
| public void KillProcess( string index ) { | |
| if ( !_procInfos.TryGet( index, out TProcInfo procInfo ) ) return; | |
| if ( procInfo == null ) return; | |
| _ = _procInfos.TryRemove( index ); | |
| if ( _procs.TryGet( index, out int id ) ) { | |
| try { | |
| Process process = ProcHelp.GetProc( id ); | |
| if ( process != null ) { | |
| process.Kill( ); | |
| } | |
| } catch ( Exception e ) { | |
| OnInternalError?.Invoke( e ); | |
| } finally { | |
| _procs.TryRemove( index ); | |
| } | |
| } | |
| if ( procInfo.ReadStandardOutput == false ) return; | |
| if ( _threadProc.TryGet( index, out Thread th ) ) { | |
| try { | |
| th.Abort( 100 ); th.Join( ); | |
| } catch ( ThreadAbortException e ) { | |
| OnInternalError?.Invoke( e ); | |
| } finally { | |
| _threadProc.TryRemove( index ); | |
| } | |
| } | |
| } | |
| protected void ReadStandardOutput( TProcInfo procInfo ) { | |
| Thread thread = new Thread( new ParameterizedThreadStart( Spwan ) ) { | |
| IsBackground = true, Priority = ThreadPriority.Normal, Name = procInfo.FileName | |
| }; | |
| thread.SetApartmentState( ApartmentState.STA ); | |
| _threadProc.TryAdd( procInfo.Index, thread ); | |
| thread.Start( procInfo ); | |
| } | |
| protected void Spwan( object _procInfo ) { | |
| TProcInfo procInfo = ( TProcInfo )_procInfo; | |
| try { | |
| using ( Process process = new Process { | |
| StartInfo = new ProcessStartInfo { | |
| Arguments = procInfo.Arguments, | |
| WorkingDirectory = procInfo.WorkingDirectory, | |
| FileName = Path.Combine( procInfo.LibDirectory, procInfo.FileName ), | |
| UseShellExecute = false, | |
| RedirectStandardOutput = true, | |
| RedirectStandardError = true, | |
| } | |
| } ) { | |
| process.OutputDataReceived += ( sender, args ) => OnData.Invoke( procInfo, args.Data ); | |
| process.ErrorDataReceived += ( sender, args ) => OnData.Invoke( procInfo, args.Data ); | |
| process.Start( ); | |
| _procs.TryAdd( procInfo.Index, process.Id ); | |
| process.BeginOutputReadLine( ); | |
| process.BeginErrorReadLine( ); | |
| process.WaitForExit( ); //you need this in order to flush the output buffer | |
| } | |
| } catch ( Exception e ) { | |
| OnInternalError?.Invoke( e ); | |
| } finally { | |
| OnExit?.Invoke( procInfo ); | |
| } | |
| } | |
| protected void Clean<TKey, TValue>( ConcurrentStorage<TKey, TValue> storage, Action<TKey> next ) { | |
| if ( storage.Count > 0 ) { | |
| storage.Each( ( key, proc ) => next( key ) ); | |
| storage.Clear( ); | |
| } | |
| } | |
| public void Dispose( ) { | |
| Clean( _procInfos, KillProcess ); | |
| _threadProc.Clear( ); _procs.Clear( ); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment