【问题标题】:C# Pausing Async Task works in one method, but not anotherC# 暂停异步任务以一种方法工作,但不是另一种方法
【发布时间】:2018-04-07 06:50:18
【问题描述】:

我有一个名为 PauseOrCancelToken 的类,它由另一个类 PauseOrCancelTokenSource 创建。 PauseOrCancelToken 基本上封装了从这篇 MSDN 博客文章中实现的 CancellationToken 和 PauseToken:https://blogs.msdn.microsoft.com/pfxteam/2013/01/13/cooperatively-pausing-async-methods/

我已经对其进行了测试,在一个简单的示例用例(我即将发布的代码中的 MethodA)中,它按预期工作。

但是,当我使用打算在生产中使用的重要代码 (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);

    }
}

【问题讨论】:

  • 你能描述一下在有问题的方法中“暂停”应该做什么,以及你观察到的是什么(通常,“预期\实际”场景)?
  • @Evk 嗨。是的,暂停应该暂停执行直到恢复,但即使在令牌被告知用户已暂停任务之后,执行似乎仍在继续。它在 MethodA 中像这样工作得很好,但在 MethodB 中却不行(在令牌传递到 ProxyTester.Start 之后),我不知道为什么。

标签: c# .net task task-parallel-library


【解决方案1】:

一般来说,ProxyTester.Start 中的代码是这样使用暂停标记的:

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

这会运行judges.Length 个任务。暂停令牌时会发生什么?好吧,实际上没什么用。所有任务继续运行,所有任务都将完成有用的工作(await judge.TestValidityAsync())。然后,当所有有用的工作都完成后,它们应该刚刚完成——它们将在await pct.PauseOrCancelIfRequested() 上暂停。我怀疑这是你想要的结果。更改顺序不会有太大帮助。

将其与“工作”示例进行比较:

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

这里的执行是顺序的(而不是像上面那样并行),并且您在每次迭代时检查暂停令牌,因此它按预期工作。

如果您希望能够在真实场景中暂停 - 不要一次启动所有这些任务,请分批运行它们(使用 SemaphoreSlim 或类似技术),并在每批后检查暂停令牌。

【讨论】:

  • 很棒的建议。你统治 Evk
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-13
  • 1970-01-01
相关资源
最近更新 更多