-
Notifications
You must be signed in to change notification settings - Fork 1
Description
In our case AddRangeAsync is the sync function to add commits. Our debug instance will implement ISyncable and call through to the data model, when adding commits it will add one at a time.
Design created with ChatGPT:
In your real code, something external calls AddRangeAsync(items).
You don’t control when or how that call is made — it just runs.
You want to intercept that AddRangeAsync so it doesn’t consume the list all at once, but instead waits for your UI to say “go to the next item.”
And importantly, your UI doesn’t “do the work” itself, it only releases the already-running AddRangeAsync loop step-by-step, and receives the result each time.
So the state machine must live inside the AddRangeAsync pipeline, but expose “continue and get last result” externally.
Pattern: Await between steps with TaskCompletionSource
Here’s a way to do that:
public class StepwiseAddController<TResult>
{
private TaskCompletionSource<TResult>? _tcs;
internal Task<TResult> WaitForNextAsync(TResult result)
{
_tcs = new TaskCompletionSource<TResult>();
_tcs.SetResult(result); // stash result for Continue() to return
return _tcs.Task;
}
public Task<TResult> ContinueAsync()
{
if (_tcs == null)
throw new InvalidOperationException("No step is pending");
var task = _tcs.Task;
_tcs = null;
return task;
}
}Your AddRangeAsync implementation
This is where the external caller still just calls await AddRangeAsync(items), but you pause after each item:
public class MyAdder
{
private readonly StepwiseAddController<string> _controller;
public MyAdder(StepwiseAddController<string> controller)
{
_controller = controller;
}
public async Task AddRangeAsync(IEnumerable<string> items)
{
foreach (var item in items)
{
// simulate your real async work
await Task.Delay(200);
var result = $"Added: {item.ToUpper()}";
// pause here, hand control to UI
await _controller.WaitForNextAsync(result);
}
}
}Driving it from your UI
Now the UI doesn’t do the work itself.
It only calls ContinueAsync() to unblock the loop and get the last result:
private StepwiseAddController<string>? _controller;
private MyAdder? _adder;
private async void btnStart_Click(object sender, EventArgs e)
{
_controller = new StepwiseAddController<string>();
_adder = new MyAdder(_controller);
var items = new[] { "apple", "banana", "cherry" };
// fire and forget; it will block after each item
_ = Task.Run(async () => await _adder.AddRangeAsync(items));
lblStatus.Text = "Ready. Click Next to step.";
btnNext.Enabled = true;
}
private async void btnNext_Click(object sender, EventArgs e)
{
if (_controller != null)
{
var result = await _controller.ContinueAsync();
lstResults.Items.Add(result);
lblStatus.Text = $"Processed {lstResults.Items.Count}";
}
}Why this works
The caller of AddRangeAsync thinks it’s running normally.
After each item, the AddRangeAsync loop blocks on _controller.WaitForNextAsync(result).
The UI calls ContinueAsync(), which returns that stored result and releases the loop to move on.