diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index daf29ff..802b64f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,4 +22,7 @@ jobs: - name: Build FrameRateTask run: dotnet build "./CSharp/FrameRateTask/FrameRateTask.csproj" -c Release - name: Run test code - run: dotnet build "./CSharp/Test/Test.csproj" -c Release + run: | + dotnet build "./CSharp/Test/Test.csproj" -c Release + dotnet build "./CSharp/TestConcurrency/TestConcurrency.csproj" -c Release + dotnet run --project "./CSharp/TestConcurrency/TestConcurrency.csproj" -c Release diff --git a/CSharp/FrameRateTask/FrameRateTask.cs b/CSharp/FrameRateTask/FrameRateTask.cs index af66d07..bfca542 100644 --- a/CSharp/FrameRateTask/FrameRateTask.cs +++ b/CSharp/FrameRateTask/FrameRateTask.cs @@ -13,6 +13,7 @@ namespace Timothy.FrameRateTask { + /// /// The class intends to execute a task that need to be executed repeatedly every less than one second and need to be accurate. /// @@ -22,7 +23,12 @@ public class FrameRateTaskExecutor /// /// The actual framerate recently. /// - public uint FrameRate { get; private set; } + public uint FrameRate + { + get => (uint)Interlocked.CompareExchange(ref frameRate, 0, 0); + private set => Interlocked.Exchange(ref frameRate, value); + } + private long frameRate; /// /// Gets a value indicating whether or not the task has finished. @@ -30,7 +36,12 @@ public class FrameRateTaskExecutor /// /// true if the task has finished; otherwise, false. /// - public bool Finished { get; private set; } = false; + public bool Finished + { + get => Interlocked.CompareExchange(ref finished, 0, 0) != 0; + set => Interlocked.Exchange(ref finished, value ? 1 : 0); + } + private int finished = 0; /// /// Gets a value indicating whether or not the task has started. @@ -38,16 +49,15 @@ public class FrameRateTaskExecutor /// /// true if the task has started; otherwise, false. /// - public bool HasExecuted { get; private set; } = false; - private object hasExecutedLock = new object(); + public bool HasExecuted { get => Interlocked.CompareExchange(ref hasExecuted, 0, 0) != 0; } + private int hasExecuted = 0; private bool TrySetExecute() { - lock (hasExecutedLock) + if (Interlocked.Exchange(ref hasExecuted, 1) != 0) { - if (HasExecuted) return false; - HasExecuted = true; - return true; + return false; } + return true; } private TResult result; @@ -67,7 +77,6 @@ public TResult Result private set => result = value; } - /// /// Gets or sets whether it allows time exceeding. /// @@ -75,7 +84,15 @@ public TResult Result /// If this property is false, the task will throw a TimeExceed exception when the task cannot finish in the given time. /// The default value is true. /// - public bool AllowTimeExceed { get; set; } = true; + public bool AllowTimeExceed + { + get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = true; /// /// It will be called once time exceeds if AllowTimeExceed is set true. @@ -83,17 +100,15 @@ public TResult Result /// /// parameter bool: If it is called because of the number of time exceeding is greater than MaxTimeExceedCount, the argument is true; if it is called because of exceeding once, the argument is false. /// - public Action TimeExceedAction { get; set; } = callByExceed => { }; - - /// - /// The TickCount when beginning the loop, - /// - public long BeginTickCount { get; private set; } = 0L; - - /// - /// The TickCount should be when ending last loop. - /// - public long LastLoopEndingTickCount { get; private set; } + public Action TimeExceedAction + { + get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = callByExceed => { }; /// /// Gets or sets the maximum count of time exceeding continuously. @@ -101,7 +116,15 @@ public TResult Result /// /// The value is 5 for default. /// - public ulong MaxTolerantTimeExceedCount { get; set; } = 5; + public ulong MaxTolerantTimeExceedCount + { + get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = 5; /// /// Start this task synchronously. @@ -155,12 +178,13 @@ public FrameRateTaskExecutor loopFunc = () => { ulong timeExceedCount = 0UL; + long lastLoopEndingTickCount, beginTickCount; - var nextTime = (LastLoopEndingTickCount = BeginTickCount = Environment.TickCount64) + timeInterval; - var endTime = BeginTickCount < long.MaxValue - maxTotalDuration ? BeginTickCount + maxTotalDuration : long.MaxValue; + var nextTime = (lastLoopEndingTickCount = beginTickCount = Environment.TickCount64) + timeInterval; + var endTime = beginTickCount < long.MaxValue - maxTotalDuration ? beginTickCount + maxTotalDuration : long.MaxValue; uint loopCnt = 0; - var nextCntTime = BeginTickCount + 1000L; + var nextCntTime = beginTickCount + 1000L; while (loopCondition() && nextTime <= endTime) { @@ -191,7 +215,7 @@ public FrameRateTaskExecutor else if (AllowTimeExceed) TimeExceedAction(false); } - LastLoopEndingTickCount = nextTime; + lastLoopEndingTickCount = nextTime; nextTime += timeInterval; ++loopCnt; if (Environment.TickCount64 >= nextCntTime) @@ -203,9 +227,9 @@ public FrameRateTaskExecutor } result = finallyReturn(); + Interlocked.MemoryBarrierProcessWide(); Finished = true; }; - } /// diff --git a/CSharp/FrameRateTask/FrameRateTask.csproj b/CSharp/FrameRateTask/FrameRateTask.csproj index 2ea4f89..7366361 100644 --- a/CSharp/FrameRateTask/FrameRateTask.csproj +++ b/CSharp/FrameRateTask/FrameRateTask.csproj @@ -2,8 +2,9 @@ netcoreapp3.1;net5.0;net6.0 - 1.0.0.0 - 1.0.0.0 + 1.1.0.0 + 1.1.0.0 + 1.1.0 https://github.com/Timothy-LiuXuefeng/FrameRateTask README.md TimothyLiuXuefeng @@ -13,6 +14,7 @@ True LICENSE.txt https://github.com/Timothy-LiuXuefeng/FrameRateTask + disable @@ -23,6 +25,14 @@ .\FrameRateTask.xml + + 5 + + + + 5 + + True diff --git a/CSharp/Test/Program.cs b/CSharp/Test/Program.cs index 4a14557..69363f4 100644 --- a/CSharp/Test/Program.cs +++ b/CSharp/Test/Program.cs @@ -11,7 +11,7 @@ static void Demo1() // The most common circumstance { Random r = new Random(); int cnt = 0; - FrameRateTaskExecutor frt = new FrameRateTaskExecutor + FrameRateTaskExecutor executor = new FrameRateTaskExecutor ( () => true, () => @@ -31,21 +31,21 @@ static void Demo1() // The most common circumstance ( () => { - while (!frt.Finished) + while (!executor.Finished) { - Console.WriteLine($"Now framerate: { frt.FrameRate }"); + Console.WriteLine($"Now framerate: { executor.FrameRate }"); Thread.Sleep(1000); } } ); - frt.Start(); + executor.Start(); } static void Demo2() // The most common circumstance. { int i = 1, sum = 0; Random r = new Random(); - FrameRateTaskExecutor frt = new FrameRateTaskExecutor + FrameRateTaskExecutor executor = new FrameRateTaskExecutor ( () => i <= 10, () => @@ -64,8 +64,8 @@ static void Demo2() // The most common circumstance. { MaxTolerantTimeExceedCount = ulong.MaxValue // Set it to ulong.MaxValue in case time exceeding could cause the result incorrect. }; - frt.Start(); - Console.WriteLine($"result: {frt.Result}"); + executor.Start(); + Console.WriteLine($"result: {executor.Result}"); } static void Demo3() { @@ -109,7 +109,7 @@ static void Demo4() { Random r = new Random(); int tm = 0; - FrameRateTaskExecutor frt = new FrameRateTaskExecutor + FrameRateTaskExecutor executor = new FrameRateTaskExecutor ( () => true, () => @@ -127,14 +127,14 @@ static void Demo4() ( () => { - while (!frt.Finished) + while (!executor.Finished) { - Console.WriteLine($"Now framerate: { frt.FrameRate }"); + Console.WriteLine($"Now framerate: { executor.FrameRate }"); Thread.Sleep(1000); } } ); - frt.Start(); + executor.Start(); } static void Demo5() @@ -143,7 +143,7 @@ static void Demo5() Random r = new Random(); int tm = 0; int cnt = 0; - FrameRateTaskExecutor frt = new FrameRateTaskExecutor + FrameRateTaskExecutor executor = new FrameRateTaskExecutor ( () => true, () => @@ -166,14 +166,14 @@ static void Demo5() ( () => { - while (!frt.Finished) + while (!executor.Finished) { - Console.WriteLine($"Now framerate: { frt.FrameRate }"); + Console.WriteLine($"Now framerate: { executor.FrameRate }"); Thread.Sleep(1000); } } ); - frt.Start(); + executor.Start(); } Console.WriteLine("You can see it is stable after it becomes clear."); @@ -185,7 +185,7 @@ static void Demo5() Random r = new Random(); int tm = 0; int cnt = 0; - FrameRateTaskExecutor frt = new FrameRateTaskExecutor + FrameRateTaskExecutor executor = new FrameRateTaskExecutor ( () => true, () => @@ -208,14 +208,14 @@ static void Demo5() ( () => { - while (!frt.Finished) + while (!executor.Finished) { - Console.WriteLine($"Now framerate: { frt.FrameRate }"); + Console.WriteLine($"Now framerate: { executor.FrameRate }"); Thread.Sleep(1000); } } ); - frt.Start(); + executor.Start(); Console.WriteLine("You can see it is NOT stable after it becomes clear."); } } diff --git a/CSharp/TestConcurrency/Program.cs b/CSharp/TestConcurrency/Program.cs new file mode 100644 index 0000000..7aac9af --- /dev/null +++ b/CSharp/TestConcurrency/Program.cs @@ -0,0 +1,87 @@ +using System; +using System.Threading; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; +using Timothy.FrameRateTask; + +namespace TestConcurrency +{ + internal class Program + { + static int Main(string[] args) + { + const int endVal = 2; + int cnt = 0; + var result = new BlockingCollection(); + var threadQueue = new BlockingCollection(); + + FrameRateTaskExecutor executor = new FrameRateTaskExecutor( + () => cnt < endVal, + () => + { + result.Add(cnt); + ++cnt; + }, + 1, + () => 8888 + ) + { AllowTimeExceed = true }; + + Action action = () => + { + for (int i = 0; i < 10; ++i) + { + var thrd = new Thread(() => + { + if (executor.TryStart()) + { + result.Add(executor.Result); + } + }); + threadQueue.Add(thrd); + thrd.Start(); + }; + }; + for (int i = 0; i < 20; ++i) + { + var thrd = new Thread(() => action()); + threadQueue.Add(thrd); + thrd.Start(); + } + + while (threadQueue.Count > 0) + { + threadQueue.TryTake(out Thread? thrd); + if (thrd is not null) + { + thrd.Join(); + } + } + for (int i = 0; i < endVal; ++i) + { + if (!result.TryTake(out int seq) || seq != i) + { + Console.WriteLine(); + if (result.Count > 0) + { + Console.Error.WriteLine($"Get error number: { seq }"); + } + else + { + Console.Error.WriteLine("Get no number!"); + } + return 1; + } + Console.Write(i.ToString() + ' '); + } + Console.WriteLine(); + if (!result.TryTake(out int res) || res != 8888) + { + Console.Error.WriteLine("Get error result!"); + return 1; + } + Console.WriteLine(res); + return 0; + } + } +} diff --git a/CSharp/TestConcurrency/TestConcurrency.csproj b/CSharp/TestConcurrency/TestConcurrency.csproj new file mode 100644 index 0000000..c5c118d --- /dev/null +++ b/CSharp/TestConcurrency/TestConcurrency.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/FrameRateTask.sln b/FrameRateTask.sln index 45441ca..d364adb 100644 --- a/FrameRateTask.sln +++ b/FrameRateTask.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31129.286 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64E26841-2083-4D77-9238-20D5C3A1F993}" ProjectSection(SolutionItems) = preProject @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "CSharp\Test\Test.cs EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSharp", "CSharp", "{C0855812-2EBC-415D-92D3-C3B100DB3203}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConcurrency", "CSharp\TestConcurrency\TestConcurrency.csproj", "{BD4BCC77-7900-46A2-A213-D4E3F611E53E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,10 @@ Global {3B3B042E-7E86-4A93-B97F-FDF3A44D3722}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B3B042E-7E86-4A93-B97F-FDF3A44D3722}.Release|Any CPU.ActiveCfg = Release|Any CPU {3B3B042E-7E86-4A93-B97F-FDF3A44D3722}.Release|Any CPU.Build.0 = Release|Any CPU + {BD4BCC77-7900-46A2-A213-D4E3F611E53E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD4BCC77-7900-46A2-A213-D4E3F611E53E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD4BCC77-7900-46A2-A213-D4E3F611E53E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD4BCC77-7900-46A2-A213-D4E3F611E53E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -38,6 +44,7 @@ Global GlobalSection(NestedProjects) = preSolution {07261E94-73FD-43C5-AAAA-8C61E7576E47} = {C0855812-2EBC-415D-92D3-C3B100DB3203} {3B3B042E-7E86-4A93-B97F-FDF3A44D3722} = {C0855812-2EBC-415D-92D3-C3B100DB3203} + {BD4BCC77-7900-46A2-A213-D4E3F611E53E} = {C0855812-2EBC-415D-92D3-C3B100DB3203} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {19AB3133-5CDE-47FD-94A8-16088C24D599} diff --git a/README.md b/README.md index adb7cb3..080d9e2 100644 --- a/README.md +++ b/README.md @@ -93,31 +93,19 @@ Copyright (C) 2022 Timothy-LiuXuefeng > > 任务的返回值。如果任务未执行完毕,将会抛出异常。 -+ `public bool AllowTimeExceed { get; set; }` ++ `public bool AllowTimeExceed { get; init; }` > Whether the engine allow time exceeding. `true` fir default. Details are under `MaxTolerantTimeExceedCount`. > > 是否允许执行超时。默认为 `true`。详情参见 `MaxTolerantTimeExceedCount`。 -+ `public Action TimeExceedAction { get; set; }` ++ `public Action TimeExceedAction { get; init; }` > It will be called when time exceeds. Details are under `MaxTolerantTimeExceedCount`. > > 将在超时后被调用。详情参见 `MaxTolerantTimeExceedCount`。 -+ `public long BeginTickCount { get; }` - - > The tick count when beginning the loop. - > - > 循环开始时的 `TickCount` 值。 - -+ `public long LastLoopEndingTickCount { get; }` - - > The tick count when last loop ends. - > - > 上一次循环结束时的 `TickCount` 值。 - -+ `public ulong MaxTolerantTimeExceedCount { get; set; }` ++ `public ulong MaxTolerantTimeExceedCount { get; init; }` > The maximum number of time exceeding in a series. `5` for default. Once time exceeds, if the number of time exceeding in a series is no more than `MaxTolerantTimeExceedCount`, `TimeExceedAction` will be called with argument `false`, otherwise, if `AllowTimeExceed` is set `true`, `TimeExceedAction` will be called with argument `true`, otherwise (`AllowTimeExceed` is set `false`), it will throw an exception. >