Skip to content

Commit 44b86b5

Browse files
authored
Merge pull request #535 from GridProtectionAlliance/XDA-83
XDA-83 Automatically enumerate a directory where the 'too many changes' error occurs
2 parents aa03c6f + 56a8d96 commit 44b86b5

3 files changed

Lines changed: 337 additions & 103 deletions

File tree

Source/Libraries/GSF.Core.Shared/GSF.Core.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
<Compile Include="$(MSBuildThisFileDirectory)Threading\StateMachine.cs" />
153153
<Compile Include="$(MSBuildThisFileDirectory)Threading\SharedTimer.cs" />
154154
<Compile Include="$(MSBuildThisFileDirectory)Threading\SynchronizedOperationBase.cs" />
155+
<Compile Include="$(MSBuildThisFileDirectory)Threading\SynchronizedTask.cs" />
155156
<Compile Include="$(MSBuildThisFileDirectory)Threading\TaskSynchronizedOperation.cs" />
156157
<Compile Include="$(MSBuildThisFileDirectory)Threading\ThreadContainerBase.cs" />
157158
<Compile Include="$(MSBuildThisFileDirectory)Threading\ThreadContainerDedicated.cs" />
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//******************************************************************************************************
2+
// SynchronizedTask.cs - Gbtc
3+
//
4+
// Copyright © 2026, Grid Protection Alliance. All Rights Reserved.
5+
//
6+
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
7+
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
8+
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
9+
// file except in compliance with the License. You may obtain a copy of the License at:
10+
//
11+
// http://opensource.org/licenses/MIT
12+
//
13+
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
14+
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
15+
// License for the specific language governing permissions and limitations.
16+
//
17+
// Code Modification History:
18+
// ----------------------------------------------------------------------------------------------------
19+
// 05/19/2026 - Stephen C. Wills
20+
// Generated original version of source code.
21+
//
22+
//******************************************************************************************************
23+
24+
using System;
25+
using System.Runtime.CompilerServices;
26+
using System.Threading.Tasks;
27+
28+
namespace GSF.Core.Threading
29+
{
30+
/// <summary>
31+
/// Represents a task that cannot run while it is already in progress.
32+
/// </summary>
33+
/// <typeparam name="T">The type of object returned by the task when it executes.</typeparam>
34+
public class SynchronizedTask<T>
35+
{
36+
/// <summary>
37+
/// Creates a new instance of the <see cref="SynchronizedTask{T}"/> class.
38+
/// </summary>
39+
/// <param name="callback">The callback to be executed when the task runs</param>
40+
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is null</exception>
41+
public SynchronizedTask(Func<Task<T>> callback)
42+
{
43+
if (callback is null)
44+
throw new ArgumentNullException(nameof(callback));
45+
46+
Callback = callback;
47+
}
48+
49+
/// <summary>
50+
/// Gets a value to indicate whether the synchronized
51+
/// task is currently executing its callback.
52+
/// </summary>
53+
public bool IsRunning
54+
{
55+
get
56+
{
57+
lock (TaskLock)
58+
return RunningTask is not null;
59+
}
60+
}
61+
62+
/// <summary>
63+
/// Gets a value to indiate whether the synchronized task
64+
/// has an additional callback that is pending execution after
65+
/// the currently running task has completed.
66+
/// </summary>
67+
public bool IsPending
68+
{
69+
get
70+
{
71+
lock (TaskLock)
72+
return PendingTask is not null;
73+
}
74+
}
75+
76+
private Func<Task<T>> Callback { get; }
77+
78+
private object TaskLock { get; } = new();
79+
private Task<T> LastCompletedTask { get; set; }
80+
private Task<T> RunningTask { get; set; }
81+
private Task<T> PendingTask { get; set; }
82+
83+
/// <summary>
84+
/// Executes the callback on another thread or marks the
85+
/// task as pending if the callback is already running.
86+
/// </summary>
87+
/// <returns>A task that completes when the callback completes.</returns>
88+
public Task<T> RunAsync()
89+
{
90+
lock (TaskLock)
91+
{
92+
if (RunningTask is null)
93+
{
94+
RunningTask = Task.Run(ExecuteCallback);
95+
return RunningTask;
96+
}
97+
98+
if (PendingTask is null)
99+
{
100+
Task<T> runningTask = RunningTask;
101+
102+
PendingTask = Task.Run(async () =>
103+
{
104+
await runningTask.ConfigureAwait(false);
105+
return await ExecuteCallback().ConfigureAwait(false);
106+
});
107+
}
108+
109+
return PendingTask;
110+
}
111+
}
112+
113+
/// <summary>
114+
/// Returns a task that completes when the currently
115+
/// running and pending tasks are both complete.
116+
/// </summary>
117+
/// <returns>A task instance.</returns>
118+
public async Task FlushAsync()
119+
{
120+
Task pendingTask;
121+
122+
lock (TaskLock)
123+
pendingTask = PendingTask ?? RunningTask ?? Task.CompletedTask;
124+
125+
try { await pendingTask; }
126+
catch { /* Ignore errors when flushing */ }
127+
}
128+
129+
/// <summary>
130+
/// Returns an awaiter used to await the most recently completed callback.
131+
/// </summary>
132+
/// <returns>An awaiter instance.</returns>
133+
public TaskAwaiter<T> GetAwaiter()
134+
{
135+
lock (TaskLock)
136+
{
137+
Task<T> task = LastCompletedTask ?? RunningTask ?? RunAsync();
138+
return task.GetAwaiter();
139+
}
140+
}
141+
142+
/// <summary>
143+
/// Configures an awaiter used to await the most recently completed callback.
144+
/// </summary>
145+
/// <param name="continueOnCapturedContext">true to attempt to marshal the continuation back to the original context captured; otherwise, false</param>
146+
/// <returns>An object used to await the most recently completed callback.</returns>
147+
public ConfiguredTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
148+
{
149+
lock (TaskLock)
150+
{
151+
Task<T> task = LastCompletedTask ?? RunningTask ?? RunAsync();
152+
return task.ConfigureAwait(continueOnCapturedContext);
153+
}
154+
}
155+
156+
private async Task<T> ExecuteCallback()
157+
{
158+
try
159+
{
160+
return await Callback().ConfigureAwait(false);
161+
}
162+
finally
163+
{
164+
lock (TaskLock)
165+
{
166+
LastCompletedTask = RunningTask;
167+
RunningTask = PendingTask;
168+
PendingTask = null;
169+
}
170+
}
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)