Skip to content

Commit 9e17073

Browse files
committed
add COnsoleAppContext.Cancel, Terminate. Better handling of OpCanceledEx
1 parent da3c3de commit 9e17073

File tree

5 files changed

+58
-30
lines changed

5 files changed

+58
-30
lines changed

ReadMe.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,9 +1039,14 @@ public class ConsoleAppContext
10391039
public MethodInfo MethodInfo { get; }
10401040
public IServiceProvider ServiceProvider { get; }
10411041
public IDictionary<string, object> Items { get; }
1042+
1043+
public void Cancel();
1044+
public void Terminate();
10421045
}
10431046
```
10441047

1048+
`Cancel()` set `CancellationToken` to canceled. Also `Terminate()` set token to cancled and terminate process(internal throws `OperationCanceledException` immediately).
1049+
10451050
ConsoleAppOptions
10461051
---
10471052
You can configure framework behaviour by ConsoleAppOptions.
@@ -1110,6 +1115,27 @@ public class MyCommand
11101115

11111116
You can set func to change this behaviour like `NameConverter = x => x.ToLower();`.
11121117

1118+
Terminate handling in Console.Read
1119+
---
1120+
ConsoleAppFramework handle terminate signal(Ctrl+C) gracefully with `ConsoleAppContext.CancellationToken`. If your application waiting with Console.Read/ReadLine/ReadKey, requires additional handling.
1121+
1122+
```csharp
1123+
// case of Console.Read/ReadLine, pressed Ctrl+C, Read returns null.
1124+
ConsoleApp.Run(args, async (ConsoleAppContext ctx) =>
1125+
{
1126+
var read = Console.ReadLine();
1127+
if (read == null) ctx.Terminate();
1128+
});
1129+
```
1130+
1131+
```csharp
1132+
// case of Console.ReadKey, can not cancel itself so use with Task.Run and WaitAsync.
1133+
ConsoleApp.Run(args, async (ConsoleAppContext ctx) =>
1134+
{
1135+
var key = await Task.Run(() => Console.ReadKey()).WaitAsync(ctx.CancellationToken);
1136+
});
1137+
```
1138+
11131139
Publish to executable file
11141140
---
11151141
[dotnet run](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-run) is useful for local development or execute in CI tool. For example in CI, git pull and execute by `dotnet run -- --options` is easy to manage and execute utilities.

sandbox/Net6Console/Program.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@
1010
// Console.ReadKey
1111
ConsoleApp.Run(args, async (ConsoleAppContext ctx) =>
1212
{
13-
var key = await Task.Run(() => Console.ReadKey(intercept: true), ctx.CancellationToken);
13+
var key = await Task.Run(() => Console.ReadKey(intercept: true)).WaitAsync(ctx.CancellationToken);
1414
Console.WriteLine(key.KeyChar);
15-
//while (true)
16-
//{
17-
// var r = Console.ReadLine();
18-
19-
// Console.WriteLine(r == null);
20-
21-
// Console.WriteLine("end:" + ctx.CancellationToken.IsCancellationRequested);
22-
//}
23-
2415
});

src/ConsoleAppFramework/ConsoleAppContext.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace ConsoleAppFramework
88
{
99
public class ConsoleAppContext
1010
{
11+
readonly CancellationTokenSource cancellationTokenSource;
12+
1113
public string?[] Arguments { get; }
1214
public DateTime Timestamp { get; }
1315
public CancellationToken CancellationToken { get; }
@@ -16,15 +18,27 @@ public class ConsoleAppContext
1618
public IServiceProvider ServiceProvider { get; }
1719
public IDictionary<string, object> Items { get; }
1820

19-
public ConsoleAppContext(string?[] arguments, DateTime timestamp, CancellationToken cancellationToken, ILogger<ConsoleApp> logger, MethodInfo methodInfo, IServiceProvider serviceProvider)
21+
public ConsoleAppContext(string?[] arguments, DateTime timestamp, CancellationTokenSource cancellationTokenSource, ILogger<ConsoleApp> logger, MethodInfo methodInfo, IServiceProvider serviceProvider)
2022
{
23+
this.cancellationTokenSource = cancellationTokenSource;
2124
Arguments = arguments;
2225
Timestamp = timestamp;
23-
CancellationToken = cancellationToken;
26+
CancellationToken = cancellationTokenSource.Token;
2427
Logger = logger;
2528
MethodInfo = methodInfo;
2629
ServiceProvider = serviceProvider;
2730
Items = new Dictionary<string, object>();
2831
}
32+
33+
public void Cancel()
34+
{
35+
cancellationTokenSource.Cancel();
36+
}
37+
38+
public void Terminate()
39+
{
40+
cancellationTokenSource.Cancel();
41+
cancellationTokenSource.Token.ThrowIfCancellationRequested();
42+
}
2943
}
3044
}

src/ConsoleAppFramework/ConsoleAppEngine.cs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ internal class ConsoleAppEngine
1515
{
1616
readonly ILogger<ConsoleApp> logger;
1717
readonly IServiceProvider provider;
18-
readonly CancellationToken cancellationToken;
18+
readonly CancellationTokenSource cancellationTokenSource;
1919
readonly ConsoleAppOptions options;
2020
readonly IServiceProviderIsService isService;
2121
readonly bool isStrict;
2222

23-
public ConsoleAppEngine(ILogger<ConsoleApp> logger, IServiceProvider provider, ConsoleAppOptions options, IServiceProviderIsService isService, CancellationToken cancellationToken)
23+
public ConsoleAppEngine(ILogger<ConsoleApp> logger, IServiceProvider provider, ConsoleAppOptions options, IServiceProviderIsService isService, CancellationTokenSource cancellationTokenSource)
2424
{
2525
this.logger = logger;
2626
this.provider = provider;
27-
this.cancellationToken = cancellationToken;
27+
this.cancellationTokenSource = cancellationTokenSource;
2828
this.options = options;
2929
this.isService = isService;
3030
this.isStrict = options.StrictOption;
@@ -134,7 +134,7 @@ async Task RunCore(Type type, MethodInfo methodInfo, object? instance, string?[]
134134
return;
135135
}
136136

137-
var ctx = new ConsoleAppContext(args, DateTime.UtcNow, cancellationToken, logger, methodInfo, provider);
137+
var ctx = new ConsoleAppContext(args, DateTime.UtcNow, cancellationTokenSource, logger, methodInfo, provider);
138138

139139
// re:create invokeArgs, merge with DI parameter.
140140
if (invokeArgs.Length != originalParameters.Length)
@@ -208,23 +208,20 @@ async Task RunCore(Type type, MethodInfo methodInfo, object? instance, string?[]
208208
}
209209
catch (Exception ex)
210210
{
211-
if (ex is OperationCanceledException operationCanceledException && operationCanceledException.CancellationToken == cancellationToken)
212-
{
213-
// NOTE: Do nothing if the exception has thrown by the CancellationToken of ConsoleAppEngine.
214-
// If the user code throws OperationCanceledException, ConsoleAppEngine should not handle that.
215-
return;
216-
}
217-
218211
if (ex is TargetInvocationException tex)
219212
{
220-
await SetFailAsync("Fail in application running on " + type.Name + "." + methodInfo.Name, tex.InnerException);
221-
return;
213+
ex = tex.InnerException ?? tex;
222214
}
223-
else
215+
216+
if (ex is OperationCanceledException operationCanceledException && operationCanceledException.CancellationToken == cancellationTokenSource.Token)
224217
{
225-
await SetFailAsync("Fail in application running on " + type.Name + "." + methodInfo.Name, ex);
218+
// NOTE: Do nothing if the exception has thrown by the CancellationToken of ConsoleAppEngine.
219+
// If the user code throws OperationCanceledException, ConsoleAppEngine should not handle that.
226220
return;
227221
}
222+
223+
await SetFailAsync("Fail in application running on " + type.Name + "." + methodInfo.Name, ex);
224+
return;
228225
}
229226

230227
logger.LogTrace("ConsoleAppEngine.Run Complete Successfully");

src/ConsoleAppFramework/ConsoleAppEngineService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public Task StartAsync(CancellationToken ct)
3636
try
3737
{
3838
self.scope = self.provider.CreateScope();
39-
var token = (self.cancellationTokenSource != null) ? self.cancellationTokenSource.Token : CancellationToken.None;
40-
var engine = ActivatorUtilities.CreateInstance<ConsoleAppEngine>(self.scope.ServiceProvider, token);
39+
if (self.cancellationTokenSource == null) self.cancellationTokenSource = new CancellationTokenSource();
40+
var engine = ActivatorUtilities.CreateInstance<ConsoleAppEngine>(self.scope.ServiceProvider, self.cancellationTokenSource!);
4141
self.runningTask = engine.RunAsync();
4242
await self.runningTask;
4343
self.runningTask = null;
@@ -63,7 +63,7 @@ public Task StartAsync(CancellationToken ct)
6363
}
6464

6565
public async Task StopAsync(CancellationToken ct)
66-
{
66+
{
6767
try
6868
{
6969
cancellationTokenSource?.Cancel();

0 commit comments

Comments
 (0)