The Cancel button should stop the run. If each proxy check catches cancellation and returns normally, the outer loop can simply advance to the next proxy. The screen then looks as though Cancel did nothing.

For modern .NET WinForms, keep one source as a form field and let cancellation reach the event handler:

private CancellationTokenSource? activeRun;

private async void startButton_Click(object sender, EventArgs e)
{
    if (activeRun is not null) return;
    using var run = new CancellationTokenSource();
    activeRun = run;
    startButton.Enabled = false;
    try
    {
        foreach (Uri proxy in configuredProxies)
        {
            run.Token.ThrowIfCancellationRequested();
            await CheckProxyAsync(proxy, run.Token);
        }
        statusLabel.Text = "Finished";
    }
    catch (OperationCanceledException) when (run.IsCancellationRequested)
    {
        statusLabel.Text = "Stopped";
    }
    finally
    {
        activeRun = null;
        startButton.Enabled = true;
    }
}

private void cancelButton_Click(object sender, EventArgs e)
    => activeRun?.Cancel();

configuredProxies is the application’s already parsed list of proxy URIs. Candidate rows from the free proxy list can feed a permitted public-target check; retain the protocol and use only types supported by this handler. The UI awaits each operation, so its message loop remains available to receive the cancel click.

Carry the token to the network

The individual check must accept that token. An abbreviated HTTP check can look like this:

private async Task CheckProxyAsync(Uri proxy, CancellationToken token)
{
    using var handler = new HttpClientHandler
    {
        Proxy = new System.Net.WebProxy(proxy),
        UseProxy = true
    };
    using var client = new HttpClient(handler);
    using var response = await client.GetAsync(
        probeUri, HttpCompletionOption.ResponseHeadersRead, token);
    response.EnsureSuccessStatusCode();
    string body = await response.Content.ReadAsStringAsync(token);
    RecordResult(proxy, body);
}

probeUri and RecordResult belong to the existing checker. Keep an appropriate response-size bound and timeout for that endpoint. A production checker can reuse clients by route; the fragment shows where cancellation travels rather than a connection-pool design.

If failed proxies should be recorded and skipped, catch expected network failures around the individual check. Let run cancellation escape to the outer handler. For parallel checks, share the run token with every worker and await their completion before enabling another run.

འབྲེལ་བའི་རྩོམ་ཡིག