diff --git a/MyThreadPoolTask/MyThreadPoolTask.Tests/.editorconfig b/MyThreadPoolTask/MyThreadPoolTask.Tests/.editorconfig
new file mode 100644
index 0000000..504dac8
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask.Tests/.editorconfig
@@ -0,0 +1,129 @@
+[*.cs]
+
+# SA1600: Elements should be documented
+dotnet_diagnostic.SA1600.severity = none
+
+# Отключить SA0001
+[*.cs]
+dotnet_diagnostic.SA0001.severity = none
+
+[*.cs]
+#### Стили именования ####
+
+# Правила именования
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Спецификации символов
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.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_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_indent_labels = one_less_than_current
+csharp_prefer_simple_default_expression = true:suggestion
+
+[*.vb]
+#### Стили именования ####
+
+# Правила именования
+
+dotnet_naming_rule.interface_should_be_начинается_с_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_начинается_с_i.symbols = interface
+dotnet_naming_rule.interface_should_be_начинается_с_i.style = начинается_с_i
+
+dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.severity = suggestion
+dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.symbols = типы
+dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.style = всечастиспрописнойбуквы
+
+dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.severity = suggestion
+dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.symbols = не_являющиеся_полем_члены
+dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.style = всечастиспрописнойбуквы
+
+# Спецификации символов
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.типы.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.типы.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.типы.required_modifiers =
+
+dotnet_naming_symbols.не_являющиеся_полем_члены.applicable_kinds = property, event, method
+dotnet_naming_symbols.не_являющиеся_полем_члены.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.не_являющиеся_полем_члены.required_modifiers =
+
+# Стили именования
+
+dotnet_naming_style.начинается_с_i.required_prefix = I
+dotnet_naming_style.начинается_с_i.required_suffix =
+dotnet_naming_style.начинается_с_i.word_separator =
+dotnet_naming_style.начинается_с_i.capitalization = pascal_case
+
+dotnet_naming_style.всечастиспрописнойбуквы.required_prefix =
+dotnet_naming_style.всечастиспрописнойбуквы.required_suffix =
+dotnet_naming_style.всечастиспрописнойбуквы.word_separator =
+dotnet_naming_style.всечастиспрописнойбуквы.capitalization = pascal_case
+
+dotnet_naming_style.всечастиспрописнойбуквы.required_prefix =
+dotnet_naming_style.всечастиспрописнойбуквы.required_suffix =
+dotnet_naming_style.всечастиспрописнойбуквы.word_separator =
+dotnet_naming_style.всечастиспрописнойбуквы.capitalization = pascal_case
+
+[*.{cs,vb}]
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
\ No newline at end of file
diff --git a/MyThreadPoolTask/MyThreadPoolTask.Tests/MyThreadPoolTask.Tests.csproj b/MyThreadPoolTask/MyThreadPoolTask.Tests/MyThreadPoolTask.Tests.csproj
new file mode 100644
index 0000000..64e1ef8
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask.Tests/MyThreadPoolTask.Tests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net9.0
+ latest
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MyThreadPoolTask/MyThreadPoolTask.Tests/ThreadPoolTests.cs b/MyThreadPoolTask/MyThreadPoolTask.Tests/ThreadPoolTests.cs
new file mode 100644
index 0000000..264fca4
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask.Tests/ThreadPoolTests.cs
@@ -0,0 +1,434 @@
+//
+// Copyright (c) Kalinin Andrew. All rights reserved.
+//
+
+namespace MyThreadPoolTask.Tests;
+
+using System.Collections.Concurrent;
+
+public class ThreadPoolTests
+{
+ private MyThreadPool? threadPool;
+
+ [TearDown]
+ public void TearDown() => this.threadPool?.Shutdown();
+
+ [Test]
+ public void Submit_SingleTask_ReturnCorrectResult()
+ {
+ this.threadPool = new MyThreadPool(4);
+ var task = this.threadPool.Submit(() => 42 * 2);
+ Assert.That(task.Result, Is.EqualTo(84));
+ Assert.That(task.IsCompleted, Is.True);
+ }
+
+ [Test]
+ public void Submit_MultipleTask_ReturnsCorrectResult()
+ {
+ this.threadPool = new MyThreadPool(5);
+ var tasks = new List>();
+ for (int i = 0; i < 10; ++i)
+ {
+ int local = i;
+ tasks.Add(this.threadPool.Submit(() => local * 2));
+ }
+
+ for (int i = 0; i < 10; ++i)
+ {
+ Assert.That(tasks[i].Result, Is.EqualTo(i * 2));
+ Assert.That(tasks[i].IsCompleted, Is.True);
+ }
+ }
+
+ [Test]
+ public void Submit_AfterShutdown_ShouldThrowsException()
+ {
+ this.threadPool = new MyThreadPool(2);
+ this.threadPool.Shutdown();
+ Assert.Throws(() => this.threadPool.Submit(() => 2313));
+ }
+
+ [Test]
+ public void Submit_TaskThrowsException_ResultThrowsAggregateException()
+ {
+ this.threadPool = new MyThreadPool(2);
+ var task = this.threadPool.Submit(() => throw new InvalidOperationException());
+ var exception = Assert.Throws(() => { var x = task.Result; });
+ Assert.That(exception.InnerException, Is.InstanceOf());
+ Assert.That(task.IsCompleted, Is.True);
+ }
+
+ [Test]
+ public void ContinueWith_ThreeTasks_ShouldReturnCorrectResult()
+ {
+ this.threadPool = new MyThreadPool(4);
+ var firstTask = this.threadPool.Submit(() => 10);
+ var secondTask = firstTask.ContinueWith(result => result * 4);
+ var thirdTask = secondTask.ContinueWith(result => result.ToString());
+ var finalResult = thirdTask.Result;
+ Assert.That(finalResult, Is.EqualTo("40"));
+ Assert.That(firstTask.IsCompleted, Is.True);
+ Assert.That(secondTask.IsCompleted, Is.True);
+ Assert.That(thirdTask.IsCompleted, Is.True);
+ }
+
+ [Test]
+ public void ThreadPool_NumberOfThreads_ShouldUseTheSpecifiedAmount()
+ {
+ const int threadCount = 5;
+ this.threadPool = new MyThreadPool(threadCount);
+ var threadId = new ConcurrentDictionary();
+ var startSignal = new ManualResetEventSlim(false);
+ var tasks = new List>();
+
+ for (int i = 0; i < threadCount; ++i)
+ {
+ var task = this.threadPool.Submit(() =>
+ {
+ threadId.TryAdd(Thread.CurrentThread.ManagedThreadId, true);
+ startSignal.Wait();
+ return true;
+ });
+ tasks.Add(task);
+ }
+
+ Thread.Sleep(700);
+
+ Assert.That(threadId.Count, Is.EqualTo(threadCount));
+
+ startSignal.Set();
+ }
+
+ [Test]
+ public void ContinueWith_MultipleContinuations_AllShouldExecute()
+ {
+ this.threadPool = new MyThreadPool(4);
+ var baseTask = this.threadPool.Submit(() => 10);
+
+ var continuation1 = baseTask.ContinueWith(x => x + 1);
+ var continuation2 = baseTask.ContinueWith(x => x + 2);
+ var continuation3 = baseTask.ContinueWith(x => x * 2);
+
+ Assert.That(continuation1.Result, Is.EqualTo(11));
+ Assert.That(continuation2.Result, Is.EqualTo(12));
+ Assert.That(continuation3.Result, Is.EqualTo(20));
+ }
+
+ [Test]
+ public void ContinueWith_OnCompletedTask_ShouldStillWork()
+ {
+ this.threadPool = new MyThreadPool(2);
+ var task = this.threadPool.Submit(() => 5);
+ var result = task.Result;
+
+ Assert.That(task.IsCompleted, Is.True);
+
+ var continuation = task.ContinueWith(x => x * 10);
+ Assert.That(continuation.Result, Is.EqualTo(50));
+ }
+
+ [Test]
+ public void Result_BeforeCompletion_ShouldBlockUntilComplete()
+ {
+ this.threadPool = new MyThreadPool(2);
+ var startSignal = new ManualResetEventSlim(false);
+ var task = this.threadPool.Submit(() =>
+ {
+ startSignal.Wait();
+ return 4;
+ });
+
+ Assert.That(task.IsCompleted, Is.False);
+
+ var resultTask = Task.Run(() => task.Result);
+
+ Thread.Sleep(100);
+ Assert.That(resultTask.IsCompleted, Is.False);
+
+ startSignal.Set();
+ Assert.That(resultTask.Result, Is.EqualTo(4));
+ Assert.That(task.IsCompleted, Is.True);
+ }
+
+ [Test]
+ public void ContinueWith_AfterShutdown_ShouldThrowException()
+ {
+ this.threadPool = new MyThreadPool(2);
+ var task = this.threadPool.Submit(() => 10);
+ var result = task.Result;
+
+ this.threadPool.Shutdown();
+
+ Assert.Throws(() => task.ContinueWith(x => x * 2));
+ this.threadPool = null;
+ }
+
+ [TestCase(0)]
+ [TestCase(-1)]
+ public void Constructor_InvalidThreadCount_ShouldThrowException(int count)
+ {
+ Assert.Throws(() => new MyThreadPool(count));
+ }
+
+ [Test]
+ public void ContinueWith_WhenShutdownDuringExecution_ShouldThrowExceptionInResult()
+ {
+ this.threadPool = new MyThreadPool(1);
+ var blockSignal = new ManualResetEventSlim(false);
+ var taskStarted = new ManualResetEventSlim(false);
+
+ var baseTask = this.threadPool.Submit(() =>
+ {
+ taskStarted.Set();
+ blockSignal.Wait();
+ return 4;
+ });
+
+ taskStarted.Wait();
+
+ var continuation = baseTask.ContinueWith(x => x * 2);
+ var shutdownThread = new Thread(() => this.threadPool!.Shutdown());
+ shutdownThread.Start();
+
+ Thread.Sleep(100);
+ blockSignal.Set();
+
+ shutdownThread.Join(TimeSpan.FromSeconds(2));
+ var exception = Assert.Throws(() => _ = continuation.Result);
+ Assert.That(exception!.InnerException, Is.InstanceOf());
+
+ this.threadPool = null;
+ }
+
+ [Test]
+ public void RaceCondition_SubmitAndShutdownConcurrently_ShouldHandleGracefully()
+ {
+ this.threadPool = new MyThreadPool(4);
+ var exceptions = new ConcurrentBag();
+ var successfulTasks = new ConcurrentBag>();
+ var shutdownStarted = new ManualResetEventSlim(false);
+ var submitThreadsReady = new CountdownEvent(10);
+
+ var submitThreads = new List();
+ for (int i = 0; i < 10; ++i)
+ {
+ int taskId = i;
+ var thread = new Thread(() =>
+ {
+ submitThreadsReady.Signal();
+ shutdownStarted.Wait();
+ try
+ {
+ var task = this.threadPool!.Submit(() => taskId);
+ successfulTasks.Add(task);
+ }
+ catch (InvalidOperationException ex)
+ {
+ exceptions.Add(ex);
+ }
+ });
+ submitThreads.Add(thread);
+ thread.Start();
+ }
+
+ submitThreadsReady.Wait();
+ shutdownStarted.Set();
+
+ var shutdownThread = new Thread(() =>
+ {
+ Thread.Sleep(5);
+ this.threadPool!.Shutdown();
+ });
+ shutdownThread.Start();
+
+ foreach (var thread in submitThreads)
+ {
+ thread.Join();
+ }
+
+ shutdownThread.Join();
+
+ Assert.That(successfulTasks.Count + exceptions.Count, Is.EqualTo(10));
+
+ this.threadPool = null;
+ }
+
+ [Test]
+ public void RaceCondition_MultipleContinueWithDuringShutdown_ShouldHandleCorrectly()
+ {
+ this.threadPool = new MyThreadPool(2);
+ var blockSignal = new ManualResetEventSlim(false);
+ var baseTask = this.threadPool.Submit(() =>
+ {
+ blockSignal.Wait();
+ return 10;
+ });
+
+ var exceptions = new ConcurrentBag();
+ var continuations = new ConcurrentBag>();
+ var continueThreadsReady = new CountdownEvent(5);
+ var shutdownSignal = new ManualResetEventSlim(false);
+
+ var continueThreads = new List();
+ for (int i = 0; i < 5; ++i)
+ {
+ int multiplier = i + 1;
+ var thread = new Thread(() =>
+ {
+ continueThreadsReady.Signal();
+ shutdownSignal.Wait();
+ try
+ {
+ var cont = baseTask.ContinueWith(x => x * multiplier);
+ continuations.Add(cont);
+ }
+ catch (InvalidOperationException ex)
+ {
+ exceptions.Add(ex);
+ }
+ });
+ continueThreads.Add(thread);
+ thread.Start();
+ }
+
+ continueThreadsReady.Wait();
+ shutdownSignal.Set();
+
+ var shutdownThread = new Thread(() =>
+ {
+ Thread.Sleep(10);
+ this.threadPool!.Shutdown();
+ });
+ shutdownThread.Start();
+
+ foreach (var thread in continueThreads)
+ {
+ thread.Join();
+ }
+
+ blockSignal.Set();
+ shutdownThread.Join();
+
+ Assert.That(continuations.Count + exceptions.Count, Is.EqualTo(5));
+
+ this.threadPool = null;
+ }
+
+ [Test]
+ public void RaceCondition_MultipleShutdownCalls_ShouldNotCrash()
+ {
+ this.threadPool = new MyThreadPool(4);
+ var tasks = new List>();
+ for (int i = 0; i < 5; ++i)
+ {
+ int local = i;
+ tasks.Add(this.threadPool.Submit(() =>
+ {
+ Thread.Sleep(50);
+ return local;
+ }));
+ }
+
+ var shutdownThreads = new List();
+ for (int i = 0; i < 5; ++i)
+ {
+ var thread = new Thread(() => this.threadPool!.Shutdown());
+ shutdownThreads.Add(thread);
+ thread.Start();
+ }
+
+ foreach (var thread in shutdownThreads)
+ {
+ thread.Join();
+ }
+
+ Assert.Pass();
+ this.threadPool = null;
+ }
+
+ [Test]
+ public void RaceCondition_ResultAccessDuringShutdown_ShouldNotDeadlock()
+ {
+ this.threadPool = new MyThreadPool(2);
+ var blockSignal = new ManualResetEventSlim(false);
+ var task = this.threadPool.Submit(() =>
+ {
+ blockSignal.Wait();
+ return 42;
+ });
+
+ var resultThread = new Thread(() =>
+ {
+ Thread.Sleep(50);
+ try
+ {
+ var result = task.Result;
+ }
+ catch
+ {
+ }
+ });
+ resultThread.Start();
+
+ var shutdownThread = new Thread(() =>
+ {
+ Thread.Sleep(100);
+ this.threadPool!.Shutdown();
+ });
+ shutdownThread.Start();
+
+ Thread.Sleep(150);
+ blockSignal.Set();
+
+ bool resultFinished = resultThread.Join(TimeSpan.FromSeconds(2));
+ bool shutdownFinished = shutdownThread.Join(TimeSpan.FromSeconds(2));
+
+ Assert.That(resultFinished, Is.True);
+ Assert.That(shutdownFinished, Is.True);
+
+ this.threadPool = null;
+ }
+
+ [Test]
+ public void RaceCondition_SubmitManyTasksConcurrently_AllShouldExecute()
+ {
+ this.threadPool = new MyThreadPool(4);
+ var tasks = new ConcurrentBag>();
+ var submitThreads = new List();
+ var startSignal = new ManualResetEventSlim(false);
+
+ for (int i = 0; i < 20; ++i)
+ {
+ int taskId = i;
+ var thread = new Thread(() =>
+ {
+ startSignal.Wait();
+ var task = this.threadPool!.Submit(() =>
+ {
+ Thread.Sleep(10);
+ return taskId * 2;
+ });
+ tasks.Add(task);
+ });
+ submitThreads.Add(thread);
+ thread.Start();
+ }
+
+ startSignal.Set();
+
+ foreach (var thread in submitThreads)
+ {
+ thread.Join();
+ }
+
+ Assert.That(tasks.Count, Is.EqualTo(20));
+
+ var results = new HashSet();
+ foreach (var task in tasks)
+ {
+ results.Add(task.Result);
+ }
+
+ Assert.That(results.Count, Is.EqualTo(20));
+ }
+}
\ No newline at end of file
diff --git a/MyThreadPoolTask/MyThreadPoolTask.Tests/stylecop.json b/MyThreadPoolTask/MyThreadPoolTask.Tests/stylecop.json
new file mode 100644
index 0000000..30fa636
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask.Tests/stylecop.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "companyName": "Kalinin Andrew",
+ "copyrightText": "Copyright (c) {companyName}. All rights reserved."
+ }
+ }
+}
\ No newline at end of file
diff --git a/MyThreadPoolTask/MyThreadPoolTask.sln b/MyThreadPoolTask/MyThreadPoolTask.sln
new file mode 100644
index 0000000..cfab85d
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35806.99
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyThreadPoolTask", "MyThreadPoolTask\MyThreadPoolTask.csproj", "{A7D8D770-5056-44C2-98D0-5973DBA29CA9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyThreadPoolTask.Tests", "MyThreadPoolTask.Tests\MyThreadPoolTask.Tests.csproj", "{2F44C96D-7958-4B1A-ADD6-AF479A3026AE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A7D8D770-5056-44C2-98D0-5973DBA29CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7D8D770-5056-44C2-98D0-5973DBA29CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7D8D770-5056-44C2-98D0-5973DBA29CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7D8D770-5056-44C2-98D0-5973DBA29CA9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F44C96D-7958-4B1A-ADD6-AF479A3026AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F44C96D-7958-4B1A-ADD6-AF479A3026AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F44C96D-7958-4B1A-ADD6-AF479A3026AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F44C96D-7958-4B1A-ADD6-AF479A3026AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CBA5D183-FDFD-4BAB-8782-23CE4B80DF14}
+ EndGlobalSection
+EndGlobal
diff --git a/MyThreadPoolTask/MyThreadPoolTask/.editorconfig b/MyThreadPoolTask/MyThreadPoolTask/.editorconfig
new file mode 100644
index 0000000..8fcdb90
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask/.editorconfig
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MyThreadPoolTask
+{
+ class _
+ {
+ }
+}
+
+# Отключить SA0001
+[*.cs]
+dotnet_diagnostic.SA0001.severity = none
+
+[*.cs]
+#### Стили именования ####
+
+# Правила именования
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Спецификации символов
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.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_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+[*.vb]
+#### Стили именования ####
+
+# Правила именования
+
+dotnet_naming_rule.interface_should_be_начинается_с_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_начинается_с_i.symbols = interface
+dotnet_naming_rule.interface_should_be_начинается_с_i.style = начинается_с_i
+
+dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.severity = suggestion
+dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.symbols = типы
+dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.style = всечастиспрописнойбуквы
+
+dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.severity = suggestion
+dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.symbols = не_являющиеся_полем_члены
+dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.style = всечастиспрописнойбуквы
+
+# Спецификации символов
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.типы.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.типы.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.типы.required_modifiers =
+
+dotnet_naming_symbols.не_являющиеся_полем_члены.applicable_kinds = property, event, method
+dotnet_naming_symbols.не_являющиеся_полем_члены.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.не_являющиеся_полем_члены.required_modifiers =
+
+# Стили именования
+
+dotnet_naming_style.начинается_с_i.required_prefix = I
+dotnet_naming_style.начинается_с_i.required_suffix =
+dotnet_naming_style.начинается_с_i.word_separator =
+dotnet_naming_style.начинается_с_i.capitalization = pascal_case
+
+dotnet_naming_style.всечастиспрописнойбуквы.required_prefix =
+dotnet_naming_style.всечастиспрописнойбуквы.required_suffix =
+dotnet_naming_style.всечастиспрописнойбуквы.word_separator =
+dotnet_naming_style.всечастиспрописнойбуквы.capitalization = pascal_case
+
+dotnet_naming_style.всечастиспрописнойбуквы.required_prefix =
+dotnet_naming_style.всечастиспрописнойбуквы.required_suffix =
+dotnet_naming_style.всечастиспрописнойбуквы.word_separator =
+dotnet_naming_style.всечастиспрописнойбуквы.capitalization = pascal_case
diff --git a/MyThreadPoolTask/MyThreadPoolTask/IMyTask.cs b/MyThreadPoolTask/MyThreadPoolTask/IMyTask.cs
new file mode 100644
index 0000000..dc8746e
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask/IMyTask.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) Kalinin Andrew. All rights reserved.
+//
+
+namespace MyThreadPoolTask;
+
+///
+/// Represents a task that returns a result and supports continuations.
+///
+/// Type of task completion result.
+public interface IMyTask
+{
+ ///
+ /// Gets a value indicating whether the task is completed.
+ ///
+ bool IsCompleted { get; }
+
+ ///
+ /// Gets the result of the task completion.
+ /// Blocks the calling thread until the task completes.
+ ///
+ ///
+ /// Thrown if the task completed with an exception.
+ ///
+ TResult Result { get; }
+
+ ///
+ /// Creates a continuation that runs after the current task is completed.
+ ///
+ /// Continuation result type.
+ /// A continuation function that accepts the result of the current task and returns a new value.
+ /// A new task that represents a continuation.
+ IMyTask ContinueWith(Func next);
+}
\ No newline at end of file
diff --git a/MyThreadPoolTask/MyThreadPoolTask/MyThreadPool.cs b/MyThreadPoolTask/MyThreadPoolTask/MyThreadPool.cs
new file mode 100644
index 0000000..98531ae
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask/MyThreadPool.cs
@@ -0,0 +1,283 @@
+//
+// Copyright (c) Kalinin Andrew. All rights reserved.
+//
+
+namespace MyThreadPoolTask;
+
+///
+/// A thread pool with a fixed number of threads to complete tasks.
+///
+public class MyThreadPool
+{
+ private readonly Thread[] workers;
+
+ private readonly Queue taskQueue = new();
+
+ private readonly object queueLock = new();
+
+ private readonly CancellationTokenSource cancellationTokenSource = new();
+
+ private int isShutdown = 0;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Number of threads in the pool.
+ /// It is thrown if threadCount is less than or equal to 0.
+ ///
+ public MyThreadPool(int threadCount)
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(threadCount, 0);
+
+ this.workers = new Thread[threadCount];
+ for (int i = 0; i < threadCount; ++i)
+ {
+ this.workers[i] = new Thread(this.WorkerLoop)
+ {
+ IsBackground = true,
+ };
+
+ this.workers[i].Start();
+ }
+ }
+
+ ///
+ /// Adds a task to the thread pool for execution.
+ ///
+ /// Task result type.
+ /// Function to execute.
+ /// Task object that allows tracking execution and getting result.
+ ///
+ /// Thrown when pool is shutdown.
+ ///
+ public IMyTask Submit(Func func)
+ {
+ ArgumentNullException.ThrowIfNull(func);
+
+ var task = new MyTask(func, this);
+ this.EnqueueAction(task.Execute);
+ return task;
+ }
+
+ ///
+ /// Stops all threads after completing current tasks.
+ ///
+ public void Shutdown()
+ {
+ if (Interlocked.CompareExchange(ref this.isShutdown, 1, 0) != 0)
+ {
+ return;
+ }
+
+ this.cancellationTokenSource.Cancel();
+ lock (this.queueLock)
+ {
+ Monitor.PulseAll(this.queueLock);
+ }
+
+ foreach (var workerThread in this.workers)
+ {
+ workerThread.Join();
+ }
+
+ this.cancellationTokenSource.Dispose();
+ }
+
+ ///
+ /// Method for adding action to execution queue.
+ ///
+ /// Action to execute.
+ /// Thrown when pool is shutdown.
+ ///
+ internal void EnqueueAction(Action action)
+ {
+ lock (this.queueLock)
+ {
+ if (this.isShutdown != 0)
+ {
+ throw new InvalidOperationException("Пул остановлен");
+ }
+
+ this.taskQueue.Enqueue(action);
+
+ Monitor.Pulse(this.queueLock);
+ }
+ }
+
+ private void WorkerLoop()
+ {
+ while (!this.cancellationTokenSource.IsCancellationRequested)
+ {
+ Action? taskAction = null;
+ lock (this.queueLock)
+ {
+ while (this.taskQueue.Count == 0)
+ {
+ if (this.cancellationTokenSource.IsCancellationRequested)
+ {
+ return;
+ }
+
+ Monitor.Wait(this.queueLock);
+
+ if (this.cancellationTokenSource.IsCancellationRequested && this.taskQueue.Count == 0)
+ {
+ return;
+ }
+ }
+
+ taskAction = this.taskQueue.Dequeue();
+ }
+
+ taskAction?.Invoke();
+ }
+ }
+
+ private class MyTask : IMyTask
+ {
+ private readonly MyThreadPool pool;
+ private readonly Lock taskLock = new();
+ private readonly ManualResetEventSlim resultReady = new(false);
+ private readonly List<(Action Execute, Action OnShutDown, Action OnParentException)> followUpActions = new();
+
+ private Func? func;
+ private TResult? result;
+ private AggregateException? exception;
+ private volatile bool isCompleted;
+
+ public MyTask(Func func, MyThreadPool pool)
+ {
+ ArgumentNullException.ThrowIfNull(func);
+ ArgumentNullException.ThrowIfNull(pool);
+ this.func = func;
+ this.pool = pool;
+ }
+
+ public bool IsCompleted => this.isCompleted;
+
+ public TResult Result
+ {
+ get
+ {
+ this.resultReady.Wait();
+
+ if (this.exception is not null)
+ {
+ throw this.exception;
+ }
+
+ return this.result!;
+ }
+ }
+
+ public IMyTask ContinueWith(Func next)
+ {
+ ArgumentNullException.ThrowIfNull(next);
+
+ lock (this.taskLock)
+ {
+ var nextTask = new MyTask(
+ () =>
+ {
+ var currentResult = this.Result;
+ return next(currentResult);
+ },
+ this.pool);
+
+ if (this.IsCompleted)
+ {
+ if (this.exception is not null)
+ {
+ nextTask.SetParentException(this.exception);
+ }
+ else
+ {
+ this.pool.EnqueueAction(nextTask.Execute);
+ }
+ }
+ else
+ {
+ this.followUpActions.Add((nextTask.Execute, nextTask.SetShutdownException, nextTask.SetParentException));
+ }
+
+ return nextTask;
+ }
+ }
+
+ internal void SetShutdownException()
+ {
+ lock (this.taskLock)
+ {
+ if (this.isCompleted)
+ {
+ return;
+ }
+
+ this.exception = new AggregateException(new InvalidOperationException("Пул остановлен"));
+ this.isCompleted = true;
+ this.resultReady.Set();
+ }
+ }
+
+ internal void SetParentException(AggregateException parentException)
+ {
+ lock (this.taskLock)
+ {
+ if (this.isCompleted)
+ {
+ return;
+ }
+
+ this.exception = parentException;
+ this.isCompleted = true;
+ this.resultReady.Set();
+ }
+ }
+
+ internal void Execute()
+ {
+ try
+ {
+ if (this.func is not null)
+ {
+ this.result = this.func();
+ }
+ }
+ catch (Exception exception)
+ {
+ this.exception = new AggregateException(exception);
+ }
+ finally
+ {
+ lock (this.taskLock)
+ {
+ this.func = null;
+ this.isCompleted = true;
+
+ this.resultReady.Set();
+
+ foreach (var (execute, onShutdown, onParentException) in this.followUpActions)
+ {
+ if (this.exception is not null)
+ {
+ onParentException(this.exception);
+ }
+ else
+ {
+ try
+ {
+ this.pool.EnqueueAction(execute);
+ }
+ catch (InvalidOperationException)
+ {
+ onShutdown();
+ }
+ }
+ }
+
+ this.followUpActions.Clear();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MyThreadPoolTask/MyThreadPoolTask/MyThreadPoolTask.csproj b/MyThreadPoolTask/MyThreadPoolTask/MyThreadPoolTask.csproj
new file mode 100644
index 0000000..085d85a
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask/MyThreadPoolTask.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Library
+ net9.0
+ enable
+ enable
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/MyThreadPoolTask/MyThreadPoolTask/stylecop.json b/MyThreadPoolTask/MyThreadPoolTask/stylecop.json
new file mode 100644
index 0000000..30fa636
--- /dev/null
+++ b/MyThreadPoolTask/MyThreadPoolTask/stylecop.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "companyName": "Kalinin Andrew",
+ "copyrightText": "Copyright (c) {companyName}. All rights reserved."
+ }
+ }
+}
\ No newline at end of file