简体   繁体   English

C#暂停异步任务在一种方法中工作,但不在另一种方法中工作

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

I have a class called PauseOrCancelToken, created by another class, PauseOrCancelTokenSource. 我有一个名为PauseOrCancelToken的类,由另一个类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/ PauseOrCancelToken基本上封装了从这篇MSDN博客文章中实现的CancellationToken和PauseToken: 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. 我已经测试了它,并在一个简单的例子中使用案例(我即将发布的代码中的MethodA),它按预期工作。

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. 但是,当我使用我打算在生产中使用的非平凡代码(MethodB / ProxyTester.Start())测试它时,它不会暂停异步任务。

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: 通常, ProxyTester.Start代码以这种方式使用暂停令牌:

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. 这将运行judges.Length任务数量。 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() ). 所有任务继续运行,所有任务都将完成其有用的工作( await judge.TestValidityAsync() )。 Then, when all useful work is done, and they should just complete - they will pause on await pct.PauseOrCancelIfRequested() . 然后,当所有有用的工作完成后,它们应该完成 - 它们将暂停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. 如果您希望能够在现实世界中暂停 - 不要一次启动所有这些任务,请批量运行(使用SemaphoreSlim或类似技术),并在每批后检查暂停令牌。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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