sillsdev / sillsdev/harmony

Build ISyncable for interactive debugging

Open
#46 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
14
Forks
4
Avg merge
3d 21h
Merged PRs (30d)
3

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating AddRangeAsync and the proposed ISyncable interface in the repository, then trace how commits are added through the data model. Compare that flow with the stepwise TaskCompletionSource design described in the issue. Done means interactive debugging can release commit additions one at a time and receive each result without changing the normal caller flow.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.