diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..2cf541a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,390 @@
+root = true
+
+# All files
+[*]
+indent_style = space
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# Xml project files
+[*.{csproj,fsproj,vbproj,proj,slnx}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,config,nuspec}]
+indent_size = 2
+
+[*.json]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+tab_width = 4
+max_line_length = 160
+
+# New line preferences
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_anonymous_function = true:suggestion
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
+csharp_style_prefer_readonly_struct = true:suggestion
+csharp_style_prefer_readonly_struct_member = true:suggestion
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_prefer_top_level_statements = true:silent
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers =
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers =
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers =
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers =
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers =
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers =
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers =
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers =
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers =
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers =
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix =
+dotnet_naming_style._camelcase.word_separator =
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.camelcase.required_prefix =
+dotnet_naming_style.camelcase.required_suffix =
+dotnet_naming_style.camelcase.word_separator =
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix =
+dotnet_naming_style.s_camelcase.word_separator =
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+
diff --git a/ChildWorker/ChildWorker.csproj b/ChildWorker/ChildWorker.csproj
index be7725d..76c315c 100644
--- a/ChildWorker/ChildWorker.csproj
+++ b/ChildWorker/ChildWorker.csproj
@@ -1,14 +1,19 @@
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
Exe
net10.0
enable
enable
-
diff --git a/ChildWorker/Program.cs b/ChildWorker/Program.cs
index ab9e931..ef27073 100644
--- a/ChildWorker/Program.cs
+++ b/ChildWorker/Program.cs
@@ -39,11 +39,7 @@ Console.CancelKeyPress += (_, e) =>
appCts.Cancel();
};
-using var pipe = new NamedPipeClientStream(
- serverName: ".",
- pipeName: pipeName,
- direction: PipeDirection.InOut,
- options: PipeOptions.Asynchronous);
+using var pipe = new NamedPipeClientStream(serverName: ".", pipeName: pipeName, direction: PipeDirection.InOut, options: PipeOptions.Asynchronous);
await pipe.ConnectAsync(appCts.Token);
@@ -62,10 +58,9 @@ async Task SendAsync(IpcFrame frame, CancellationToken ct)
}
await SendAsync(
- new IpcFrame(
- Kind: IpcKinds.Hello,
- Payload: IpcProtocol.ToJsonElement(new { childId = childId.Value, pid = Environment.ProcessId })),
- appCts.Token);
+ new IpcFrame(Kind: IpcKinds.Hello, Payload: IpcProtocol.ToJsonElement(new { childId = childId.Value, pid = Environment.ProcessId })),
+ appCts.Token
+);
CancellationTokenSource? workCts = null;
Task? workTask = null;
@@ -102,38 +97,64 @@ async Task StartWorkAsync(string correlationId, int steps, int delayMs, Cancella
workCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var wct = workCts.Token;
- workTask = Task.Run(async () =>
- {
- await SendAsync(new IpcFrame(
- Kind: IpcKinds.Log,
- CorrelationId: correlationId,
- Payload: IpcProtocol.ToJsonElement(new { level = "info", message = $"Child {childId}: work started ({steps} steps)." })), wct);
-
- for (int i = 1; i <= steps; i++)
+ workTask = Task.Run(
+ async () =>
{
- wct.ThrowIfCancellationRequested();
- await Task.Delay(delayMs, wct);
-
- double percent = (i * 100.0) / steps;
- await SendAsync(new IpcFrame(
- Kind: IpcKinds.Progress,
- CorrelationId: correlationId,
- Payload: IpcProtocol.ToJsonElement(new { step = i, total = steps, percent })), wct);
-
- if (i % Math.Max(1, steps / 5) == 0)
- {
- await SendAsync(new IpcFrame(
+ await SendAsync(
+ new IpcFrame(
Kind: IpcKinds.Log,
CorrelationId: correlationId,
- Payload: IpcProtocol.ToJsonElement(new { level = "debug", message = $"Child {childId}: reached step {i}/{steps}." })), wct);
- }
- }
+ Payload: IpcProtocol.ToJsonElement(new { level = "info", message = $"Child {childId}: work started ({steps} steps)." })
+ ),
+ wct
+ );
- await SendAsync(new IpcFrame(
- Kind: IpcKinds.Result,
- CorrelationId: correlationId,
- Payload: IpcProtocol.ToJsonElement(new { message = $"Child {childId}: work finished." })), wct);
- }, wct);
+ for (int i = 1; i <= steps; i++)
+ {
+ wct.ThrowIfCancellationRequested();
+ await Task.Delay(delayMs, wct);
+
+ double percent = (i * 100.0) / steps;
+ await SendAsync(
+ new IpcFrame(
+ Kind: IpcKinds.Progress,
+ CorrelationId: correlationId,
+ Payload: IpcProtocol.ToJsonElement(
+ new
+ {
+ step = i,
+ total = steps,
+ percent,
+ }
+ )
+ ),
+ wct
+ );
+
+ if (i % Math.Max(1, steps / 5) == 0)
+ {
+ await SendAsync(
+ new IpcFrame(
+ Kind: IpcKinds.Log,
+ CorrelationId: correlationId,
+ Payload: IpcProtocol.ToJsonElement(new { level = "debug", message = $"Child {childId}: reached step {i}/{steps}." })
+ ),
+ wct
+ );
+ }
+ }
+
+ await SendAsync(
+ new IpcFrame(
+ Kind: IpcKinds.Result,
+ CorrelationId: correlationId,
+ Payload: IpcProtocol.ToJsonElement(new { message = $"Child {childId}: work finished." })
+ ),
+ wct
+ );
+ },
+ wct
+ );
}
try
@@ -149,33 +170,41 @@ try
switch (frame.Kind)
{
case IpcKinds.Ping:
- await SendAsync(new IpcFrame(
- Kind: IpcKinds.Pong,
- CorrelationId: frame.CorrelationId,
- Payload: IpcProtocol.ToJsonElement(new { childId = childId.Value })), appCts.Token);
+ await SendAsync(
+ new IpcFrame(Kind: IpcKinds.Pong, CorrelationId: frame.CorrelationId, Payload: IpcProtocol.ToJsonElement(new { childId = childId.Value })),
+ appCts.Token
+ );
break;
case IpcKinds.StartWork:
- {
- var payload = IpcProtocol.FromJsonElement(frame.Payload) ?? new StartWorkPayload();
- string corr = frame.CorrelationId ?? Guid.NewGuid().ToString("N");
- await StartWorkAsync(corr, payload.Steps, payload.DelayMs, appCts.Token);
- break;
- }
+ {
+ var payload = IpcProtocol.FromJsonElement(frame.Payload) ?? new StartWorkPayload();
+ string corr = frame.CorrelationId ?? Guid.NewGuid().ToString("N");
+ await StartWorkAsync(corr, payload.Steps, payload.DelayMs, appCts.Token);
+ break;
+ }
case IpcKinds.CancelWork:
await CancelWorkAsync();
- await SendAsync(new IpcFrame(
- Kind: IpcKinds.Log,
- CorrelationId: frame.CorrelationId ?? currentWorkCorrelationId,
- Payload: IpcProtocol.ToJsonElement(new { level = "info", message = $"Child {childId}: work cancelled." })), appCts.Token);
+ await SendAsync(
+ new IpcFrame(
+ Kind: IpcKinds.Log,
+ CorrelationId: frame.CorrelationId ?? currentWorkCorrelationId,
+ Payload: IpcProtocol.ToJsonElement(new { level = "info", message = $"Child {childId}: work cancelled." })
+ ),
+ appCts.Token
+ );
break;
default:
- await SendAsync(new IpcFrame(
- Kind: IpcKinds.Error,
- CorrelationId: frame.CorrelationId,
- Payload: IpcProtocol.ToJsonElement(new { message = $"Unknown command kind '{frame.Kind}'." })), appCts.Token);
+ await SendAsync(
+ new IpcFrame(
+ Kind: IpcKinds.Error,
+ CorrelationId: frame.CorrelationId,
+ Payload: IpcProtocol.ToJsonElement(new { message = $"Unknown command kind '{frame.Kind}'." })
+ ),
+ appCts.Token
+ );
break;
}
}
diff --git a/CommIpc/Class1.cs b/CommIpc/Class1.cs
deleted file mode 100644
index 88c6fc0..0000000
--- a/CommIpc/Class1.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace CommIpc;
-
-public class Class1
-{
-
-}
diff --git a/CommIpc/CommIpc.csproj b/CommIpc/CommIpc.csproj
index b760144..189d05f 100644
--- a/CommIpc/CommIpc.csproj
+++ b/CommIpc/CommIpc.csproj
@@ -1,9 +1,14 @@
-
net10.0
enable
enable
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/CommIpc/IpcFrame.cs b/CommIpc/IpcFrame.cs
index b6d2628..8533159 100644
--- a/CommIpc/IpcFrame.cs
+++ b/CommIpc/IpcFrame.cs
@@ -4,12 +4,7 @@ namespace CommIpc;
///
/// Single protocol unit sent over the pipe. This is intentionally generic.
-///
+///
/// Transport framing: 4-byte little-endian length prefix + UTF-8 JSON bytes.
///
-public sealed record IpcFrame(
- string Kind,
- string? CorrelationId = null,
- JsonElement? Payload = null,
- DateTimeOffset? Timestamp = null
-);
+public sealed record IpcFrame(string Kind, string? CorrelationId = null, JsonElement? Payload = null, DateTimeOffset? Timestamp = null);
diff --git a/CommIpc/IpcJson.cs b/CommIpc/IpcJson.cs
index 468217c..5ff82b4 100644
--- a/CommIpc/IpcJson.cs
+++ b/CommIpc/IpcJson.cs
@@ -4,9 +4,5 @@ namespace CommIpc;
internal static class IpcJson
{
- public static readonly JsonSerializerOptions Options = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- WriteIndented = false
- };
+ public static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false };
}
diff --git a/CommIpc/IpcProtocol.cs b/CommIpc/IpcProtocol.cs
index 392bcce..7784035 100644
--- a/CommIpc/IpcProtocol.cs
+++ b/CommIpc/IpcProtocol.cs
@@ -8,10 +8,7 @@ public static class IpcProtocol
// Keep the prototype safe from accidental runaway memory usage.
public const int DefaultMaxFrameBytes = 4 * 1024 * 1024; // 4 MiB
- public static async Task WriteFrameAsync(
- Stream stream,
- IpcFrame frame,
- CancellationToken cancellationToken = default)
+ public static async Task WriteFrameAsync(Stream stream, IpcFrame frame, CancellationToken cancellationToken = default)
{
// Always include a timestamp if the sender didn't set one.
if (frame.Timestamp is null)
@@ -32,10 +29,7 @@ public static class IpcProtocol
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
- public static async Task ReadFrameAsync(
- Stream stream,
- int maxFrameBytes = DefaultMaxFrameBytes,
- CancellationToken cancellationToken = default)
+ public static async Task ReadFrameAsync(Stream stream, int maxFrameBytes = DefaultMaxFrameBytes, CancellationToken cancellationToken = default)
{
byte[] header = ArrayPool.Shared.Rent(4);
try
@@ -50,10 +44,7 @@ public static class IpcProtocol
throw new EndOfStreamException("Unexpected end of stream while reading frame header.");
}
- int len = header[0]
- | (header[1] << 8)
- | (header[2] << 16)
- | (header[3] << 24);
+ int len = header[0] | (header[1] << 8) | (header[2] << 16) | (header[3] << 24);
if (len < 0)
{
@@ -106,18 +97,12 @@ public static class IpcProtocol
return element.Value.Deserialize(IpcJson.Options);
}
- private static async Task ReadExactOrEofAsync(
- Stream stream,
- byte[] buffer,
- int offset,
- int count,
- CancellationToken cancellationToken)
+ private static async Task ReadExactOrEofAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int total = 0;
while (total < count)
{
- int n = await stream.ReadAsync(buffer.AsMemory(offset + total, count - total), cancellationToken)
- .ConfigureAwait(false);
+ int n = await stream.ReadAsync(buffer.AsMemory(offset + total, count - total), cancellationToken).ConfigureAwait(false);
if (n == 0)
{
return total; // EOF
diff --git a/CommTester.slnx b/CommTester.slnx
index 2343ce5..b8bc4ab 100644
--- a/CommTester.slnx
+++ b/CommTester.slnx
@@ -1,4 +1,7 @@
+
+
+
diff --git a/ParentAvalonia/App.axaml.cs b/ParentAvalonia/App.axaml.cs
index 700daf0..1659d70 100644
--- a/ParentAvalonia/App.axaml.cs
+++ b/ParentAvalonia/App.axaml.cs
@@ -20,4 +20,4 @@ public partial class App : Application
base.OnFrameworkInitializationCompleted();
}
-}
\ No newline at end of file
+}
diff --git a/ParentAvalonia/ChildSession.cs b/ParentAvalonia/ChildSession.cs
index baf0ec7..8339e11 100644
--- a/ParentAvalonia/ChildSession.cs
+++ b/ParentAvalonia/ChildSession.cs
@@ -118,10 +118,26 @@ public sealed class ChildSession : NotifyBase, IAsyncDisposable
}
catch { }
- try { Pipe.Dispose(); } catch { }
- try { Process.Dispose(); } catch { }
- try { LifetimeCts.Dispose(); } catch { }
- try { _writeLock.Dispose(); } catch { }
+ try
+ {
+ Pipe.Dispose();
+ }
+ catch { }
+ try
+ {
+ Process.Dispose();
+ }
+ catch { }
+ try
+ {
+ LifetimeCts.Dispose();
+ }
+ catch { }
+ try
+ {
+ _writeLock.Dispose();
+ }
+ catch { }
await Task.CompletedTask;
}
diff --git a/ParentAvalonia/MainViewModel.cs b/ParentAvalonia/MainViewModel.cs
index 12ad2e3..57fe2ad 100644
--- a/ParentAvalonia/MainViewModel.cs
+++ b/ParentAvalonia/MainViewModel.cs
@@ -45,7 +45,8 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
string corr = Guid.NewGuid().ToString("N");
await SelectedChild.SendAsync(
new IpcFrame(IpcKinds.Ping, CorrelationId: corr, Payload: IpcProtocol.ToJsonElement(new { from = "parent" })),
- SelectedChild.LifetimeCts.Token);
+ SelectedChild.LifetimeCts.Token
+ );
SelectedChild.AddLog($"[parent] -> ping ({corr})");
}
@@ -62,11 +63,9 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
SelectedChild.ProgressPercent = 0;
await SelectedChild.SendAsync(
- new IpcFrame(
- IpcKinds.StartWork,
- CorrelationId: corr,
- Payload: IpcProtocol.ToJsonElement(new { steps, delayMs })),
- SelectedChild.LifetimeCts.Token);
+ new IpcFrame(IpcKinds.StartWork, CorrelationId: corr, Payload: IpcProtocol.ToJsonElement(new { steps, delayMs })),
+ SelectedChild.LifetimeCts.Token
+ );
SelectedChild.AddLog($"[parent] -> startWork (corr={corr}, steps={steps}, delayMs={delayMs})");
}
@@ -79,9 +78,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
}
string corr = SelectedChild.CurrentWorkId ?? Guid.NewGuid().ToString("N");
- await SelectedChild.SendAsync(
- new IpcFrame(IpcKinds.CancelWork, CorrelationId: corr),
- SelectedChild.LifetimeCts.Token);
+ await SelectedChild.SendAsync(new IpcFrame(IpcKinds.CancelWork, CorrelationId: corr), SelectedChild.LifetimeCts.Token);
SelectedChild.AddLog($"[parent] -> cancelWork (corr={corr})");
}
@@ -94,7 +91,8 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
direction: PipeDirection.InOut,
maxNumberOfServerInstances: 1,
transmissionMode: PipeTransmissionMode.Byte,
- options: PipeOptions.Asynchronous);
+ options: PipeOptions.Asynchronous
+ );
Task waitForConnection = server.WaitForConnectionAsync(cancellationToken);
@@ -124,11 +122,10 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
string debugDll = Path.Combine(solutionRoot, "ChildWorker", "bin", "Debug", "net10.0", "ChildWorker.dll");
string releaseDll = Path.Combine(solutionRoot, "ChildWorker", "bin", "Release", "net10.0", "ChildWorker.dll");
- string childDll = File.Exists(debugDll)
- ? debugDll
- : File.Exists(releaseDll)
- ? releaseDll
- : string.Empty;
+ string childDll =
+ File.Exists(debugDll) ? debugDll
+ : File.Exists(releaseDll) ? releaseDll
+ : string.Empty;
string fileName;
string arguments;
@@ -150,7 +147,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
- CreateNoWindow = true
+ CreateNoWindow = true,
};
var p = new Process { StartInfo = psi, EnableRaisingEvents = true };
@@ -216,46 +213,46 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
switch (frame.Kind)
{
case IpcKinds.Hello:
- {
- var hello = IpcProtocol.FromJsonElement(frame.Payload);
- session.AddLog($"[child] hello: id={hello?.ChildId}, pid={hello?.Pid}");
- break;
- }
+ {
+ var hello = IpcProtocol.FromJsonElement(frame.Payload);
+ session.AddLog($"[child] hello: id={hello?.ChildId}, pid={hello?.Pid}");
+ break;
+ }
case IpcKinds.Pong:
session.AddLog($"[child] pong ({frame.CorrelationId})");
break;
case IpcKinds.Log:
- {
- var log = IpcProtocol.FromJsonElement(frame.Payload);
- session.AddLog($"[child:{log?.Level ?? "info"}] {log?.Message}");
- break;
- }
+ {
+ var log = IpcProtocol.FromJsonElement(frame.Payload);
+ session.AddLog($"[child:{log?.Level ?? "info"}] {log?.Message}");
+ break;
+ }
case IpcKinds.Progress:
+ {
+ var prog = IpcProtocol.FromJsonElement(frame.Payload);
+ if (prog is not null)
{
- var prog = IpcProtocol.FromJsonElement(frame.Payload);
- if (prog is not null)
- {
- session.ProgressPercent = prog.Percent;
- session.Status = $"Working ({prog.Step}/{prog.Total})";
- }
- break;
+ session.ProgressPercent = prog.Percent;
+ session.Status = $"Working ({prog.Step}/{prog.Total})";
}
+ break;
+ }
case IpcKinds.Result:
- {
- var res = IpcProtocol.FromJsonElement(frame.Payload);
- session.ProgressPercent = 100;
- session.Status = "Idle";
- session.AddLog($"[child] result: {res?.Message}");
- break;
- }
+ {
+ var res = IpcProtocol.FromJsonElement(frame.Payload);
+ session.ProgressPercent = 100;
+ session.Status = "Idle";
+ session.AddLog($"[child] result: {res?.Message}");
+ break;
+ }
case IpcKinds.Error:
- {
- var err = IpcProtocol.FromJsonElement(frame.Payload);
- session.Status = "Error";
- session.AddLog($"[child] error: {err?.Message}");
- break;
- }
+ {
+ var err = IpcProtocol.FromJsonElement(frame.Payload);
+ session.Status = "Error";
+ session.AddLog($"[child] error: {err?.Message}");
+ break;
+ }
default:
session.AddLog($"[child] {frame.Kind} ({frame.CorrelationId})");
break;
@@ -268,15 +265,23 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
foreach (var child in Children.ToArray())
{
- try { await child.DisposeAsync(); } catch { }
+ try
+ {
+ await child.DisposeAsync();
+ }
+ catch { }
}
_shutdownCts.Dispose();
}
private sealed record HelloPayload(int ChildId, int Pid);
+
private sealed record LogPayload(string Level, string Message);
+
private sealed record ProgressPayload(int Step, int Total, double Percent);
+
private sealed record ResultPayload(string Message);
+
private sealed record ErrorPayload(string Message);
}
diff --git a/ParentAvalonia/MainWindow.axaml.cs b/ParentAvalonia/MainWindow.axaml.cs
index e3ef760..d51ac54 100644
--- a/ParentAvalonia/MainWindow.axaml.cs
+++ b/ParentAvalonia/MainWindow.axaml.cs
@@ -15,32 +15,60 @@ public partial class MainWindow : Window
Closed += async (_, __) =>
{
- try { await _vm.DisposeAsync(); } catch { }
+ try
+ {
+ await _vm.DisposeAsync();
+ }
+ catch { }
};
}
private async void StartChildren_Click(object? sender, RoutedEventArgs e)
{
- try { await _vm.StartChildrenAsync(count: 3); }
- catch (Exception ex) { await MessageBoxAsync(ex.Message); }
+ try
+ {
+ await _vm.StartChildrenAsync(count: 3);
+ }
+ catch (Exception ex)
+ {
+ await MessageBoxAsync(ex.Message);
+ }
}
private async void PingSelected_Click(object? sender, RoutedEventArgs e)
{
- try { await _vm.PingSelectedAsync(); }
- catch (Exception ex) { await MessageBoxAsync(ex.Message); }
+ try
+ {
+ await _vm.PingSelectedAsync();
+ }
+ catch (Exception ex)
+ {
+ await MessageBoxAsync(ex.Message);
+ }
}
private async void StartWork_Click(object? sender, RoutedEventArgs e)
{
- try { await _vm.StartWorkSelectedAsync(); }
- catch (Exception ex) { await MessageBoxAsync(ex.Message); }
+ try
+ {
+ await _vm.StartWorkSelectedAsync();
+ }
+ catch (Exception ex)
+ {
+ await MessageBoxAsync(ex.Message);
+ }
}
private async void CancelWork_Click(object? sender, RoutedEventArgs e)
{
- try { await _vm.CancelWorkSelectedAsync(); }
- catch (Exception ex) { await MessageBoxAsync(ex.Message); }
+ try
+ {
+ await _vm.CancelWorkSelectedAsync();
+ }
+ catch (Exception ex)
+ {
+ await MessageBoxAsync(ex.Message);
+ }
}
private async Task MessageBoxAsync(string message)
@@ -61,10 +89,10 @@ public partial class MainWindow : Window
{
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
Content = "OK",
- IsDefault = true
- }
- }
- }
+ IsDefault = true,
+ },
+ },
+ },
};
if (dlg.Content is StackPanel sp && sp.Children.LastOrDefault() is Button ok)
@@ -74,4 +102,4 @@ public partial class MainWindow : Window
await dlg.ShowDialog(this);
}
-}
\ No newline at end of file
+}
diff --git a/ParentAvalonia/NotifyBase.cs b/ParentAvalonia/NotifyBase.cs
index 6be9ca2..b6ea4d1 100644
--- a/ParentAvalonia/NotifyBase.cs
+++ b/ParentAvalonia/NotifyBase.cs
@@ -7,8 +7,8 @@ public abstract class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
- protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
- => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
diff --git a/ParentAvalonia/ParentAvalonia.csproj b/ParentAvalonia/ParentAvalonia.csproj
index 48e3422..ff2bfa9 100644
--- a/ParentAvalonia/ParentAvalonia.csproj
+++ b/ParentAvalonia/ParentAvalonia.csproj
@@ -19,6 +19,10 @@
None
All
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/ParentAvalonia/Program.cs b/ParentAvalonia/Program.cs
index 917b1e4..4afd439 100644
--- a/ParentAvalonia/Program.cs
+++ b/ParentAvalonia/Program.cs
@@ -1,5 +1,5 @@
-using Avalonia;
-using System;
+using System;
+using Avalonia;
namespace ParentAvalonia;
@@ -9,13 +9,8 @@ class Program
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
- public static void Main(string[] args) => BuildAvaloniaApp()
- .StartWithClassicDesktopLifetime(args);
+ public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
- public static AppBuilder BuildAvaloniaApp()
- => AppBuilder.Configure()
- .UsePlatformDetect()
- .WithInterFont()
- .LogToTrace();
+ public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure().UsePlatformDetect().WithInterFont().LogToTrace();
}