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.
>