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(); }