Skip to content

Commit 9a65f37

Browse files
authored
closes EvilBeaver#860 Обработчик нажатия Ctrl+C в консоли
Обработчик прерывания консольного приложения по Ctrl+C
2 parents 0e0368a + 8556d55 commit 9a65f37

File tree

10 files changed

+133
-42
lines changed

10 files changed

+133
-42
lines changed

src/OneScript.StandardLibrary/Tasks/BackgroundTasksManager.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,10 @@ public BackgroundTask Execute(IRuntimeContextInstance target, string methodName,
4747
var task = new BackgroundTask(target, methodName, parameters);
4848
_tasks.Add(task);
4949

50-
var eventProcessor = MachineInstance.Current.EventProcessor;
51-
5250
var taskCreationOptions = longRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.None;
5351
var worker = new Task(() =>
5452
{
5553
MachineInstance.Current.SetMemory(_runtimeContext);
56-
MachineInstance.Current.EventProcessor = eventProcessor;
5754
var debugger = _runtimeContext.Services.TryResolve<IDebugController>();
5855
debugger?.AttachToThread();
5956
try

src/OneScript.StandardLibrary/Text/ConsoleContext.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,44 @@ namespace OneScript.StandardLibrary.Text
1919
/// <summary>
2020
/// Класс представляет собой инструмент доступа к системной консоли.
2121
/// Предназначен для низкоуровнего манипулирования выводом в консоль.
22+
///
23+
/// Поддерживается регистрация обработчика для нажатия Ctrl+C.
24+
/// Обработчик регистрируется для события с именем CancelKeyPressed. Стоит учитывать, что обработчик вызывается
25+
/// в отдельном потоке и необходимо помнить о потокобезопасности общих данных.
26+
///
2227
/// </summary>
28+
/// <example>
29+
/// Перем Хватит;
30+
///
31+
/// Процедура Обработчик(Отказ)
32+
/// Сообщить("Обработчик остановки вызван", СтатусСообщения.Важное);
33+
/// Отказ = Истина; // не даем системе убить процесс
34+
/// Хватит = Истина; // выставим свой флаг завершения цикла
35+
/// КонецПроцедуры
36+
///
37+
/// Хватит = Ложь;
38+
/// ДобавитьОбработчик Консоль.CancelKeyPressed, Обработчик;
39+
///
40+
/// Пока Не Хватит Цикл
41+
/// Сообщить("Пинг", СтатусСообщения.Информация);
42+
/// Приостановить(1000);
43+
/// Сообщить("Понг", СтатусСообщения.Информация);
44+
/// Приостановить(1000);
45+
/// КонецЦикла;
46+
///
47+
/// Сообщить("Мягкая остановка процесса", СтатусСообщения.Информация);
48+
/// </example>
2349
[ContextClass("Консоль", "Console")]
2450
public class ConsoleContext : AutoContext<ConsoleContext>
2551
{
52+
private readonly ExecutionContext _executionContext;
53+
54+
public ConsoleContext(ExecutionContext executionContext)
55+
{
56+
_executionContext = executionContext;
57+
Console.CancelKeyPress += ConsoleOnCancelKeyPress;
58+
}
59+
2660
[ContextProperty("НажатаКлавиша", "KeyPressed")]
2761
public bool HasKey => Console.KeyAvailable;
2862

@@ -244,5 +278,31 @@ public void SetError(IValue target)
244278
var writer = new StreamWriter(stream.GetUnderlyingStream());
245279
Console.SetError(writer);
246280
}
281+
282+
public const string ConsoleCancelKeyEvent = "CancelKeyPressed";
283+
284+
private void ConsoleOnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
285+
{
286+
if (e.SpecialKey != ConsoleSpecialKey.ControlC)
287+
return;
288+
289+
// Сначала проверим, что события вообще включены
290+
var eventProcessor = _executionContext.Services.TryResolve<IEventProcessor>();
291+
if (eventProcessor == null)
292+
return;
293+
294+
MachineInstance.Current.SetMemory(_executionContext);
295+
var debugger = _executionContext.Services.TryResolve<IDebugController>();
296+
debugger?.AttachToThread();
297+
298+
var cancelVar = Variable.Create(BslBooleanValue.False, "Cancel");
299+
var reference = Variable.CreateReference(cancelVar, "Cancel");
300+
var args = new IValue[] { reference };
301+
302+
// Вызываем обработчик. Исключение в обработчике никак отдельно не обрабатываем.
303+
eventProcessor.HandleEvent(this, ConsoleCancelKeyEvent, args);
304+
305+
e.Cancel = reference.Value.AsBoolean();
306+
}
247307
}
248308
}

src/OneScript.StandardLibrary/Text/ConsoleProvider.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ This Source Code Form is subject to the terms of the
77

88
using System;
99
using OneScript.Contexts;
10+
using OneScript.DependencyInjection;
11+
using OneScript.Types;
1012
using ScriptEngine.Machine;
1113
using ScriptEngine.Machine.Contexts;
1214

@@ -15,10 +17,11 @@ namespace OneScript.StandardLibrary.Text
1517
[GlobalContext(Category = "Работа с консолью")]
1618
public class ConsoleProvider : GlobalContextBase<ConsoleProvider>
1719
{
18-
private readonly ConsoleContext _console = new ConsoleContext();
20+
private readonly ConsoleContext _console;
1921

20-
private ConsoleProvider()
22+
private ConsoleProvider(ExecutionContext executionContext)
2123
{
24+
_console = new ConsoleContext(executionContext);
2225
}
2326

2427
public override void OnAttach(out IVariable[] variables, out BslMethodInfo[] methods)
@@ -45,9 +48,9 @@ public override void SetPropValue(int propNum, IValue newVal)
4548
}
4649
}
4750

48-
public static ConsoleProvider CreateInstance()
51+
public static ConsoleProvider CreateInstance(ExecutionContext executionContext)
4952
{
50-
return new ConsoleProvider();
53+
return new ConsoleProvider(executionContext);
5154
}
5255
}
5356
}

src/ScriptEngine.HostedScript/DefaultEventProcessor.cs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private struct Handler
2626

2727
private class HandlersList : IEnumerable<Handler>
2828
{
29-
private List<Handler> _handlers = new List<Handler>();
29+
private readonly List<Handler> _handlers = new List<Handler>();
3030

3131
public void Add(ScriptDrivenObject target, string methodName)
3232
{
@@ -58,7 +58,10 @@ IEnumerator IEnumerable.GetEnumerator()
5858
}
5959
}
6060

61-
private Dictionary<IRuntimeContextInstance, Dictionary<string, HandlersList>> _registeredHandlers = new Dictionary<IRuntimeContextInstance, Dictionary<string, HandlersList>>();
61+
private readonly Dictionary<IRuntimeContextInstance, Dictionary<string, HandlersList>> _registeredHandlers
62+
= new Dictionary<IRuntimeContextInstance, Dictionary<string, HandlersList>>();
63+
64+
private readonly object _subscriptionLock = new object();
6265

6366
public void AddHandler(
6467
IRuntimeContextInstance eventSource,
@@ -68,19 +71,23 @@ public void AddHandler(
6871
{
6972
if (!(handlerTarget is ScriptDrivenObject handlerScript))
7073
throw RuntimeException.InvalidArgumentType("handlerTarget");
71-
72-
if (!_registeredHandlers.TryGetValue(eventSource, out var handlers))
74+
75+
lock (_subscriptionLock)
7376
{
74-
handlers = new Dictionary<string, HandlersList>();
75-
_registeredHandlers[eventSource] = handlers;
76-
}
77-
78-
if (!handlers.TryGetValue(eventName, out var handlersList)) {
79-
handlersList = new HandlersList();
80-
handlers[eventName] = handlersList;
77+
if (!_registeredHandlers.TryGetValue(eventSource, out var handlers))
78+
{
79+
handlers = new Dictionary<string, HandlersList>();
80+
_registeredHandlers[eventSource] = handlers;
81+
}
82+
83+
if (!handlers.TryGetValue(eventName, out var handlersList))
84+
{
85+
handlersList = new HandlersList();
86+
handlers[eventName] = handlersList;
87+
}
88+
89+
handlersList.Add(handlerScript, handlerMethod);
8190
}
82-
83-
handlersList.Add(handlerScript, handlerMethod);
8491
}
8592

8693
public void RemoveHandler(
@@ -92,26 +99,37 @@ public void RemoveHandler(
9299
if (!(handlerTarget is ScriptDrivenObject handlerScript))
93100
throw RuntimeException.InvalidArgumentType("handlerTarget");
94101

95-
if (!_registeredHandlers.TryGetValue(eventSource, out var handlers))
102+
lock (_subscriptionLock)
96103
{
97-
return;
98-
}
99-
100-
if (handlers.TryGetValue(eventName, out var handlersList)) {
101-
handlersList.Remove(handlerScript, handlerMethod);
104+
if (!_registeredHandlers.TryGetValue(eventSource, out var handlers))
105+
{
106+
return;
107+
}
108+
109+
if (handlers.TryGetValue(eventName, out var handlersList))
110+
{
111+
handlersList.Remove(handlerScript, handlerMethod);
112+
}
102113
}
103114
}
104115

105116
public void HandleEvent(IRuntimeContextInstance eventSource, string eventName, IValue[] eventArgs)
106117
{
107-
if (!_registeredHandlers.TryGetValue(eventSource, out var handlers))
108-
return;
109-
110-
if (!handlers.TryGetValue(eventName, out var handlersList)) {
111-
return;
118+
HandlersList handlersLocalCopy;
119+
120+
lock (_subscriptionLock)
121+
{
122+
123+
if (!_registeredHandlers.TryGetValue(eventSource, out var handlers))
124+
return;
125+
126+
if (!handlers.TryGetValue(eventName, out handlersLocalCopy))
127+
{
128+
return;
129+
}
112130
}
113-
114-
foreach (var handler in handlersList)
131+
132+
foreach (var handler in handlersLocalCopy)
115133
{
116134
handler.Method(eventArgs);
117135
}

src/ScriptEngine.HostedScript/Extensions/EngineBuilderExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,5 +124,11 @@ public static IEngineBuilder UseNativeRuntime(this IEngineBuilder builder)
124124

125125
return builder;
126126
}
127+
128+
public static IEngineBuilder UseEventHandlers(this IEngineBuilder builder)
129+
{
130+
builder.Services.RegisterSingleton<IEventProcessor, DefaultEventProcessor>();
131+
return builder;
132+
}
127133
}
128134
}

src/ScriptEngine.HostedScript/HostedScriptEngine.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This Source Code Form is subject to the terms of the
1212
using OneScript.Commons;
1313
using OneScript.Compilation;
1414
using OneScript.Contexts;
15+
using OneScript.DependencyInjection;
1516
using OneScript.Execution;
1617
using OneScript.StandardLibrary;
1718
using OneScript.StandardLibrary.Tasks;
@@ -127,6 +128,8 @@ private void DefineConstants(ICompilerFrontend compilerSvc)
127128
}
128129
}
129130

131+
public IServiceContainer Services => _engine.Services;
132+
130133
public void SetGlobalEnvironment(IHostApplication host, SourceCode src)
131134
{
132135
_globalCtx.ApplicationHost = host;

src/ScriptEngine.HostedScript/Process.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ public int Start()
3030

3131
try
3232
{
33-
MachineInstance.Current.EventProcessor = new DefaultEventProcessor();
3433
_engine.UpdateContexts();
3534
_engine.NewObject(_module);
3635
exitCode = 0;

src/ScriptEngine/Machine/Contexts/UserScriptContextInstance.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ protected override void CallOwnProcedure(int index, IValue[] arguments)
255255
if (eventArgs == null)
256256
eventArgs = new IValue[0];
257257

258-
MachineInstance.Current.EventProcessor?.HandleEvent(this, eventName, eventArgs);
258+
MachineInstance.Current.Memory.Services.TryResolve<IEventProcessor>()?.HandleEvent(this, eventName, eventArgs);
259259
}
260260

261261
protected override int GetOwnVariableCount()

src/ScriptEngine/Machine/MachineInstance.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This Source Code Form is subject to the terms of the
1212
using System.Linq;
1313
using System.Reflection;
1414
using System.Runtime.CompilerServices;
15+
using System.Threading;
1516
using System.Threading.Tasks;
1617
using OneScript.Commons;
1718
using OneScript.Compilation.Binding;
@@ -45,11 +46,17 @@ public class MachineInstance
4546
// актуален в момент останова машины
4647
private IList<ExecutionFrameInfo> _fullCallstackCache;
4748
private ScriptInformationContext _debugInfo;
49+
50+
// кешированный обработчик событий
51+
private readonly Lazy<IEventProcessor> _eventProcessor;
4852

4953
private MachineInstance()
5054
{
5155
InitCommands();
5256
Reset();
57+
58+
_eventProcessor = new Lazy<IEventProcessor>(() => _mem.Services.TryResolve<IEventProcessor>(),
59+
LazyThreadSafetyMode.None);
5360
}
5461

5562
public event EventHandler<MachineStoppedEventArgs> MachineStopped;
@@ -68,6 +75,7 @@ public void SetMemory(ExecutionContext memory)
6875
_codeStatCollector = _mem.Services.TryResolve<ICodeStatCollector>();
6976
_globalContexts = _mem.GlobalNamespace.AttachedContexts.Select(x => new AttachedContext(x))
7077
.ToArray();
78+
7179
}
7280

7381
public void UpdateGlobals()
@@ -302,11 +310,8 @@ private StackRuntimeModule CompileCached(string code, Func<string, StackRuntimeM
302310
}
303311

304312
#endregion
305-
306-
/// <summary>
307-
/// Обработчик событий, генерируемых классами прикладной логики.
308-
/// </summary>
309-
public IEventProcessor EventProcessor { get; set; }
313+
314+
private IEventProcessor EventProcessor => _eventProcessor.Value ?? throw new InvalidOperationException("Host does not support events");
310315

311316
private ScriptInformationContext CurrentScript
312317
{
@@ -2521,7 +2526,7 @@ private static ExecutionFrameInfo FrameInfo(StackRuntimeModule module, Execution
25212526
// multithreaded instance
25222527
[ThreadStatic]
25232528
private static MachineInstance _currentThreadWorker;
2524-
2529+
25252530
private static void SetCurrentMachineInstance(MachineInstance current)
25262531
=> _currentThreadWorker = current;
25272532

src/oscript/ConsoleHostBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private static void BuildUpWithIoC(IEngineBuilder builder)
5151
.UseImports()
5252
.UseFileSystemLibraries()
5353
.UseNativeRuntime()
54-
;
54+
.UseEventHandlers();
5555
}
5656
}
5757
}

0 commit comments

Comments
 (0)