public void Run(ActionTrigger trigger, TriggerArgs args, int muteMod) { //perf.first(); Action actionWrapper = () => { var o = trigger.options; var oldOpt = o.thread == TOThread.New ? default : opt.scope.all(inherit: false); try { _MuteMod(ref muteMod); string sTrigger = null; long startTime = 0; //perf.next(); if (script.role == SRole.MiniProgram) { sTrigger = trigger.ToString(); Api.QueryPerformanceCounter(out startTime); print.TaskEvent_("AS " + sTrigger, startTime, trigger.sourceFile, trigger.sourceLine); //perf.next(); } var baArgs = new TOBAArgs(args); //struct #if true o.before?.Invoke(baArgs); #else if (o.before != null) { bool called = false; if (t_beforeCalled == null) { t_beforeCalled = new List <Action <bool> > { o.before } } ; else if (!t_beforeCalled.Contains(o.before)) { t_beforeCalled.Add(o.before); } else { called = true; } o.before(!called); } #endif try { //perf.nw(); trigger.Run(args); if (sTrigger != null) { print.TaskEvent_("AE", startTime, trigger.sourceFile, trigger.sourceLine); } } catch (Exception e1) { if (sTrigger != null) { print.TaskEvent_("AF", startTime, trigger.sourceFile, trigger.sourceLine); } baArgs.Exception = e1; print.it(e1); } o.after?.Invoke(baArgs); } catch (Exception e2) { print.it(e2); } finally { oldOpt.Dispose(); if (o.flags.Has(TOFlags.Single)) { _d.TryRemove(trigger, out _); } if (o.thread != TOThread.Main) { toolbar.TriggerActionEndedInNonmainThread_(); } } }; //never mind: we should not create actionWrapper if cannot run. But such cases are rare. Fast and small, about 64 bytes. var opt1 = trigger.options; int threadId = opt1.thread; if (threadId >= 0) //dedicated thread { _Thread h = null; foreach (var v in _a) { if (v.id == threadId) { h = v; break; } } if (h == null) { _a.Add(h = new _Thread(threadId)); } if (h.RunAction(actionWrapper, trigger)) { return; } } else if (threadId == TOThread.Main) { actionWrapper(); return; //note: can reenter. Probably it is better than to cancel if already running. } else { bool canRun = true; bool single = opt1.flags.Has(TOFlags.Single); if (single) { _d ??= new(); if (_d.TryGetValue(trigger, out var tt)) { switch (tt) { case Thread thread: if (thread.IsAlive) { canRun = false; } break; case Task task: //print.it(task.Status); switch (task.Status) { case TaskStatus.RanToCompletion: case TaskStatus.Faulted: case TaskStatus.Canceled: break; default: canRun = false; break; } break; } } } if (canRun) { if (threadId == TOThread.New) { var thread = new Thread(actionWrapper.Invoke) { IsBackground = true }; //if (!opt1.flags.Has(TOFlags.MtaThread)) thread.SetApartmentState(ApartmentState.STA); if (single) { _d[trigger] = thread; } try { thread.Start(); } catch (OutOfMemoryException) { //too many threads, probably 32-bit process if (single) { _d.TryRemove(trigger, out _); } _OutOfMemory(); //SHOULDDO: before starting thread, warn if there are too many action threads. // In 32-bit process normally fails at ~3000 threads. // Unlikely to fail in 64-bit process, but at ~15000 threads starts to hang temporarily, which causes hook timeout, slow mouse, other anomalies. } } else //thread pool { var task = new Task(actionWrapper); if (single) { _d[trigger] = task; } task.Start(); } return; } } if (muteMod != 0) { ThreadPool.QueueUserWorkItem(_ => _MuteMod(ref muteMod)); } }
public void Run(ActionTrigger trigger, TriggerArgs args, int muteMod) { Action actionWrapper = () => { var opt = trigger.options; try { _MuteMod(ref muteMod); string sTrigger = null; if (ATask.Role == ATRole.MiniProgram) { Util.Log_.Run.Write($"Trigger action started. Trigger: {sTrigger = trigger.ToString()}"); } AOpt.Reset(); var baArgs = new TOBAArgs(args); //struct #if true opt.before?.Invoke(baArgs); #else if (opt.before != null) { bool called = false; if (t_beforeCalled == null) { t_beforeCalled = new List <Action <bool> > { opt.before } } ; else if (!t_beforeCalled.Contains(opt.before)) { t_beforeCalled.Add(opt.before); } else { called = true; } opt.before(!called); } #endif try { trigger.Run(args); if (sTrigger != null) { Util.Log_.Run.Write($"Trigger action ended. Trigger: {sTrigger}"); } } catch (Exception e1) { if (sTrigger != null) { Util.Log_.Run.Write($"Unhandled exception in trigger action. Trigger: {sTrigger}. Exception: {e1.ToStringWithoutStack()}"); } baArgs.Exception = e1; AOutput.Write(e1); } opt.after?.Invoke(baArgs); } catch (Exception e2) { AOutput.Write(e2); } finally { if (opt.thread < -1 && opt.ifRunning == 0) { _d.TryRemove(trigger, out _); } } }; //never mind: we should not create actionWrapper if cannot run. But such cases are rare. Fast and small, about 64 bytes. int threadId = trigger.options.thread; if (threadId >= 0) //dedicated thread { _Thread h = null; foreach (var v in _a) { if (v.id == threadId) { h = v; break; } } if (h == null) { _a.Add(h = new _Thread(threadId)); } if (h.RunAction(actionWrapper, trigger)) { return; } } else if (threadId == -1) //main thread { actionWrapper(); return; //note: can reenter. Probably it is better than to cancel if already running. } else { bool canRun = true; bool singleInstance = trigger.options.ifRunning == 0; if (singleInstance) { _d ??= new System.Collections.Concurrent.ConcurrentDictionary <ActionTrigger, object>(); if (_d.TryGetValue(trigger, out var tt)) { switch (tt) { case Thread thread: if (thread.IsAlive) { canRun = false; } break; case Task task: //AOutput.Write(task.Status); switch (task.Status) { case TaskStatus.RanToCompletion: case TaskStatus.Faulted: case TaskStatus.Canceled: break; default: canRun = false; break; } break; } } } if (canRun) { if (threadId == -2) //new thread { var thread = new Thread(actionWrapper.Invoke) { IsBackground = true }; thread.SetApartmentState(ApartmentState.STA); if (singleInstance) { _d[trigger] = thread; } try { thread.Start(); } catch (OutOfMemoryException) { //too many threads, probably 32-bit process if (singleInstance) { _d.TryRemove(trigger, out _); } _OutOfMemory(); //SHOULDDO: before starting thread, warn if there are too many action threads. // In 32-bit process normally fails at ~3000 threads. // Unlikely to fail in 64-bit process, but at ~15000 threads starts to hang temporarily, which causes hook timeout, slow mouse, other anomalies. } } else //thread pool { var task = new Task(actionWrapper); if (singleInstance) { _d[trigger] = task; } task.Start(); } return; } } if (muteMod != 0) { ThreadPool.QueueUserWorkItem(_ => _MuteMod(ref muteMod)); } }