diff --git a/Homework2.Tests/Homework2.Tests.csproj b/Homework2.Tests/Homework2.Tests.csproj new file mode 100644 index 0000000..0ba5997 --- /dev/null +++ b/Homework2.Tests/Homework2.Tests.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/Homework2.Tests/LazyTests.cs b/Homework2.Tests/LazyTests.cs new file mode 100644 index 0000000..3e50bc2 --- /dev/null +++ b/Homework2.Tests/LazyTests.cs @@ -0,0 +1,81 @@ +namespace Homework2.Tests; + +public class Tests +{ + private static IEnumerable LazyForResultTest => new TestCaseData[] + { + new TestCaseData(new LazySingleThread(() => 2)), + new TestCaseData(new LazyMultyThread(() => 2)), + }; + + private static IEnumerable LazyForMultipleCallTest => new TestCaseData[] + { + new TestCaseData(new LazySingleThread(() => new object())), + new TestCaseData(new LazyMultyThread(() => new object())), + }; + + private static IEnumerable LazyForExceptionTest => new TestCaseData[] + { + new TestCaseData(new LazySingleThread(() => throw new Exception())), + new TestCaseData(new LazyMultyThread(() => throw new Exception())), + }; + + [TestCaseSource(nameof(LazyForResultTest))] + public void SingleLazyCallReturnResultTest(ILazy lazy) + { + Assert.That(lazy.Get(), Is.EqualTo(2)); + } + + [TestCaseSource(nameof(LazyForMultipleCallTest))] + public void MultipleLazyCallReturnsSameResultTest(ILazy lazy) + { + object fstCallObject = lazy.Get()!; + object sndCallObject = lazy.Get()!; + Assert.That(fstCallObject, Is.EqualTo(sndCallObject)); + } + + [TestCaseSource(nameof(LazyForExceptionTest))] + public void LazyThrowsExceptionTest(ILazy lazy) + { + Assert.Throws(() => lazy.Get()); + } + + [Test] + public static void MultiThreadsReturnSameResultLazyTest() + { + var lazy = new LazyMultyThread(() => new object()); + var threads = new Thread[20]; + + var manualResetEvent = new ManualResetEvent(false); + + var arrayOfResults = new object[20]; + for (int i = 0; i < 20; i++) + { + int localI = i; + threads[i] = new Thread(() => + { + manualResetEvent.WaitOne(); + arrayOfResults[localI] = lazy.Get()!; + }); + } + + foreach (var thread in threads) + { + thread.Start(); + } + + manualResetEvent.Set(); + + foreach (var thread in threads) + { + thread.Join(); + } + + for (int i = 0; i < 19; i++) + { + Assert.That(arrayOfResults[i], Is.EqualTo(arrayOfResults[i + 1])); + } + } + + +} \ No newline at end of file diff --git a/Homework2.Tests/Usings.cs b/Homework2.Tests/Usings.cs new file mode 100644 index 0000000..9a28bd8 --- /dev/null +++ b/Homework2.Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; diff --git a/Homework2/Homework2.csproj b/Homework2/Homework2.csproj new file mode 100644 index 0000000..4658cbf --- /dev/null +++ b/Homework2/Homework2.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/Homework2/Homework2.sln b/Homework2/Homework2.sln new file mode 100644 index 0000000..ab274a3 --- /dev/null +++ b/Homework2/Homework2.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1706.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Homework2", "Homework2.csproj", "{62DE394A-FA36-4AAE-92D4-E758A1023000}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Homework2.Tests", "..\Homework2.Tests\Homework2.Tests.csproj", "{9D5DF180-207E-4939-BC5E-873DA34309DA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {62DE394A-FA36-4AAE-92D4-E758A1023000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62DE394A-FA36-4AAE-92D4-E758A1023000}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62DE394A-FA36-4AAE-92D4-E758A1023000}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62DE394A-FA36-4AAE-92D4-E758A1023000}.Release|Any CPU.Build.0 = Release|Any CPU + {9D5DF180-207E-4939-BC5E-873DA34309DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D5DF180-207E-4939-BC5E-873DA34309DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D5DF180-207E-4939-BC5E-873DA34309DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D5DF180-207E-4939-BC5E-873DA34309DA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2604FC35-D54E-4E08-88FE-BC3F2DAE3C53} + EndGlobalSection +EndGlobal diff --git a/Homework2/ILazy.cs b/Homework2/ILazy.cs new file mode 100644 index 0000000..1a54c4f --- /dev/null +++ b/Homework2/ILazy.cs @@ -0,0 +1,9 @@ +namespace Homework2; + +/// +/// Interface for multy and single threads Lazy calculation. +/// +public interface ILazy +{ + T? Get(); +} \ No newline at end of file diff --git a/Homework2/LazyMultyThread.cs b/Homework2/LazyMultyThread.cs new file mode 100644 index 0000000..4befad3 --- /dev/null +++ b/Homework2/LazyMultyThread.cs @@ -0,0 +1,63 @@ +namespace Homework2; + +/// +/// Lazy calculation class which works correctly with multithreading. +/// +public class LazyMultyThread : ILazy +{ + private Func? supplier; + private bool isFirstCall = true; + private Exception? supplierException = null; + private readonly object lockObject; + private T? result = default; + + /// + /// Create new incstance of LazyMultThread class. + /// + /// Function that Lazy calculate + public LazyMultyThread(Func calculation) + { + supplier = calculation; + lockObject = new(); + } + + /// + /// Method that returns calculated one time value. + /// + public T? Get() + { + if (Volatile.Read(ref isFirstCall)) + { + lock (lockObject) + { + if (Volatile.Read(ref isFirstCall)) + { + try + { + if (supplier == null) + { + throw new InvalidOperationException(); + } + result = supplier(); + return result; + } + catch (Exception exception) + { + supplierException = exception; + } + finally + { + supplier = null; + Volatile.Write(ref isFirstCall, false);//volatile write, result, supplier = null, supplierException in memory + } + } + } + } + if (Volatile.Read(ref supplierException) == null) //volatile read, values ​​in memory are now in the cache of all threads + { + return result; + } + throw supplierException!; + } + +} \ No newline at end of file diff --git a/Homework2/LazySingleThread.cs b/Homework2/LazySingleThread.cs new file mode 100644 index 0000000..336a571 --- /dev/null +++ b/Homework2/LazySingleThread.cs @@ -0,0 +1,53 @@ +namespace Homework2; + +/// +/// Lazy calculation class which works with singlethreading. +/// +public class LazySingleThread : ILazy +{ + private Func? supplier; + private bool isFirstCall = true; + private Exception? supplierException = null; + private T? result = default; + + /// + /// Create new incstance of LazySingleThread class. + /// + /// Function that Lazy calculate + public LazySingleThread(Func calculation) + { + supplier = calculation; + } + + /// + /// Method that returns calculated one time value. + /// + public T? Get() + { + if (isFirstCall) + { + try + { + if (supplier == null) + { + throw new InvalidOperationException(); + } + result = supplier(); + } + catch (Exception exception) + { + supplierException = exception; + } + finally + { + supplier = null; + isFirstCall = false; + } + } + if (supplierException == null) + { + return result; + } + throw supplierException; + } +} \ No newline at end of file