简体   繁体   中英

C# Pausing Async Task works in one method, but not another

I have a class called PauseOrCancelToken, created by another class, PauseOrCancelTokenSource. PauseOrCancelToken basically encapsulates both a CancellationToken and PauseToken implemented from this MSDN blog post: https://blogs.msdn.microsoft.com/pfxteam/2013/01/13/cooperatively-pausing-async-methods/

I have tested it, and in a simple example use case (MethodA in the code I am about to post), it works as intended.

However, when I test it with non-trivial code that I intend to use in production (MethodB/ProxyTester.Start()), it is not pausing the async task.

public partial class PauseCancelForm : Form
{

    private PauseOrCancelTokenSource pcts = new PauseOrCancelTokenSource();

    public PauseCancelForm()
    {
        InitializeComponent();
    }

    private void StartButton_Click(object sender, EventArgs e)
    {
        Task.Run(() =>
        {
            MethodA(pcts.Token).Wait();
        });
    }

    private async Task MethodA(PauseOrCancelToken pct)
    {
        //Pauses as intended when the pause button is clicked.
        for (int i = 0; i < 10000; i++)
        {
            Console.WriteLine(i);
            await Task.Delay(1000);
            await pct.PauseOrCancelIfRequested();
        }
    }

    private async Task MethodB(PauseOrCancelToken pct)
    {
        //Doesn't pause.
        var proxies = new List<Proxy>();
        var judges = new List<ProxyJudge>();
        for (int i = 0; i < 10000; i++)
        {
            proxies.Add(new Proxy("127.0.0." + RandomUtility.GetRandomInt(1, 100), 8888));
        }

        judges.Add(new ProxyJudge("http://azenv.net"));

        await ProxyTester.Start(proxies, judges, pct);
    }

    private void PauseButton_Click(object sender, EventArgs e)
    {
        pcts.Pause();
    }

    private void StopButton_Click(object sender, EventArgs e)
    {
        pcts.Cancel();
    }

    private void ResumeButton_Click(object sender, EventArgs e)
    {
        pcts.Resume();
    }
}

public class PauseOrCancelTokenSource
{

    private PauseTokenSource pts = new PauseTokenSource();
    private CancellationTokenSource cts = new CancellationTokenSource();
    public PauseOrCancelToken Token { get { return new PauseOrCancelToken(pts, cts); } }

    public void Pause()
    {
        pts.IsPaused = true;
    }

    public void Resume()
    {
        pts.IsPaused = false;
    }

    public void Cancel()
    {
        cts.Cancel();
    }
}

public class PauseOrCancelToken
{
    private PauseToken pt;
    private CancellationToken ct;

    public PauseOrCancelToken(PauseTokenSource pts, CancellationTokenSource cts)
    {
        this.pt = pts.Token;
        this.ct = cts.Token;
    }

    public async Task PauseIfRequested()
    {
        await pt.WaitWhilePausedAsync();
    }

    public void CancelIfRequested()
    {
        ct.ThrowIfCancellationRequested();
    }

    public async Task PauseOrCancelIfRequested()
    {
        await PauseIfRequested();
        CancelIfRequested();
    }
}

public class ProxyTester
{

    public async static Task Start(List<Proxy> proxies, List<ProxyJudge> judges, PauseOrCancelToken pct, List<ProxyTest> tests = null)
    {
        if (tests == null)
        {
            tests = new List<ProxyTest>();
        }

        //Get external IP to check if proxy is anonymous.
        var publicIp = await WebUtility.GetPublicIP();

        //Validate proxy judges.
        var tasks = new List<Task>();
        foreach (var judge in judges)
        {
            tasks.Add(Task.Run(async () => {
                judge.IsValid = await judge.TestValidityAsync();
                await pct.PauseOrCancelIfRequested();
            }));
        }

        await Task.WhenAll(tasks);

        var validJudges = from judge in judges
                            where judge.IsValid
                            select judge;

        if (validJudges.Count() == 0)
        {
            throw new Exception("No valid judges loaded.");
        }

        //Validate proxy tests.
        tasks.Clear();
        foreach (var test in tests)
        {
            tasks.Add(Task.Run(async () => {
                test.IsValid = await test.TestValidityAsync();
                await pct.PauseOrCancelIfRequested();
            }));
        }

        await Task.WhenAll(tasks);

        var validTests = from test in tests
                            where test.IsValid
                            select test;

        var count = 0;
        //Test proxies with a random, valid proxy judge.  If valid, test with all valid proxy tests.
        tasks.Clear();
        foreach (var proxy in proxies)
        {
            tasks.Add(Task.Run(async () =>
            {
                proxy.IsValid = await proxy.TestValidityAsync(validJudges.ElementAt(RandomUtility.GetRandomInt(0, validJudges.Count())));
                count++;
                Console.WriteLine(count);
                await pct.PauseOrCancelIfRequested();

                if (proxy.IsValid)
                {
                    proxy.TestedSites.AddRange(validTests);
                    var childTasks = new List<Task>();
                    foreach (var test in validTests)
                    {
                        childTasks.Add(Task.Run(async () =>
                        {
                            proxy.TestedSites.ElementAt(proxy.TestedSites.IndexOf(test)).IsValid = await proxy.TestValidityAsync(test);
                            await pct.PauseOrCancelIfRequested();
                        }));
                    }

                    await Task.WhenAll(childTasks);
                }
            }));
        }


        await Task.WhenAll(tasks);

    }
}

In general, code in ProxyTester.Start uses pause token this way:

foreach (var judge in judges)
{
    tasks.Add(Task.Run(async () => {
        judge.IsValid = await judge.TestValidityAsync();
        await pct.PauseOrCancelIfRequested();
    }));
}

This runs judges.Length number of tasks. What happens when you pause token? Well, nothing useful actually. All tasks continue to run, and all of them will complete their useful work ( await judge.TestValidityAsync() ). Then, when all useful work is done, and they should just complete - they will pause on await pct.PauseOrCancelIfRequested() . I doubt that is the result you desire. Changing the order won't help much.

Compare that to "working" example:

for (int i = 0; i < 10000; i++)
{
    Console.WriteLine(i);
    await Task.Delay(1000);
    await pct.PauseOrCancelIfRequested();
}

Here execution is sequential (and not parallel like above) and you check pause token every iteration, so it works as expected.

If you want to be able to pause in your real world scenario - don't start all those tasks at once, run them in batches (with SemaphoreSlim or similar technique), and check pause token after each batch.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM