How it works
An asynchronous operation can expose intermediate progress separately from its eventual completion. IProgress<T> provides a Report method for that communication. Work reports the percentage after each of four completed parts, while its returned Task remains the authoritative signal that the overall operation has finished.
The sample's InlineProgress implementation calls its delegate synchronously. That makes the printed ordering deterministic for teaching: twenty-five, fifty, seventy-five, one hundred, and then done. The built-in Progress<T> has context-capture and dispatch behavior that callers must understand; an arbitrary progress implementation need not report on the worker's current execution context.
Progress information is observational. A report of one hundred percent does not by itself prove that cleanup, final validation, or durable saving has completed. Await the operation's task for its actual terminal outcome. An operation may also fail after reporting substantial partial progress.
Avoid using progress callbacks to mutate unrelated core calculation state. Reporting should not become a hidden dependency that determines whether the operation produces a correct result. A slow or throwing reporter can affect the operation if invoked synchronously, so decide how reporting errors and expensive UI updates are handled.
Practice by changing the total number of parts to three and choosing an integer percentage policy that still ends at one hundred. Add a progress record containing Completed and Total instead of calculating display percentages inside the worker. Let the presentation layer choose formatting while the operation reports factual counts.
Finally, consider how often a real operation should report. Updating a user interface for every byte or every tight-loop iteration can cost more than the useful work. Choose meaningful milestones or throttling at an appropriate boundary, preserve the final completion signal, and test zero-work and failed-work cases as carefully as a successful full run.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
await Work(new InlineProgress(value => Console.WriteLine($"{value}%")));
Console.WriteLine("done");
static async Task Work(IProgress<int> progress) {
for (int part = 1; part <= 4; part++) {
await Task.Yield();
progress.Report(part * 25);
}
}
class InlineProgress : IProgress<int> {
private readonly Action<int> report;
public InlineProgress(Action<int> report) { this.report = report; }
public void Report(int value) => report(value);
}
Expected output
25% 50% 75% 100% done
IProgress defines reporting; this sample uses a deliberately synchronous reporter.