PauseOrCancelTokenSource


Я добавил эту реализацию PauseToken и PauseTokenSource в мой проект от этого в MSDN блоге: совместно останавливаясь асинхронные методы

Так как я обычно будет проходить в PauseToken и CancellationToken в async-методов, я пытаюсь инкапсулировать их обоих в один класс, PauseOrCancelToken, созданные PauseOrCancelTokenSource. Я не совсем уверен, если я реализовал это правильно, хотя он работает как задумано. Просто интересно, если я упустил что-то важное, что кто-то может заметить.

public partial class PauseCancelForm : Form
{

    private PauseOrCancelTokenSource pcts = new PauseOrCancelTokenSource();

    public PauseCancelForm()
    {
        InitializeComponent();
    }

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

    private async Task DoSomeAsyncStuff(PauseOrCancelToken pct)
    {
        var proxies = new List<Proxy>();
        var judges = new List<ProxyJudge>();

        for (int i = 0; i < 100; i++)
        {
            proxies.Add(new Proxy("127.0.0." + RandomUtility.GetRandomInt(1, 5), 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();
    }
}

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 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 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.PauseIfRequested();
                pct.CancelIfRequested();
            }));
        }

        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.PauseIfRequested();
                pct.CancelIfRequested();
            }));
        }

        await Task.WhenAll(tasks);

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

        //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())));
                await pct.PauseIfRequested();
                pct.CancelIfRequested();

                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.PauseIfRequested();
                            pct.CancelIfRequested();
                        }));
                    }

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


        await Task.WhenAll(tasks);

    }
}


Комментарии
1 ответ

Единственная важная вещь, я вижу, отсутствует, это позволить PauseOrCancelTokenSource реализации IDisposable сделать это можно утилизировать CancellationTokenSource поля правильно. Существует много мнений о необходимости распоряжаться CancellationTokens, но вы должны по крайней мере дать клиенту возможность это сделать. Видеть этого.


PauseOrCancelToken должен иметь открытый CancellationToken CancellationToken => ct; чтобы сделать это возможным, чтобы передать токен отмены для асинхронных вызовов внешних API, как await Task.Delay(1000, pct.CancellationToken); поэтому они могут ответить на запрос отмены.


Вы могли бы объединить PauseIfRequested() и CancelIfRequested() в один способ, потому что вы постоянно называете их обоих в то же время в любом случае.


Моя модификация для вашего занятия будет что-то вроде:

  public class PauseOrCancelTokenSource : IDisposable
{

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 Cancel()
{
cts.Cancel();
}

public bool IsDisposed => cts == null;

public void Dispose()
{
if (cts != null)
{
cts.Dispose();
cts = null;
}
}
}

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

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

public CancellationToken CancellationToken => ct;

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

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

public async Task PauseOrCancel()
{
ct.ThrowIfCancellationRequested();
await pt.WaitWhilePausedAsync();
}

}

Отказ от ответственности: я не проверял должным образом, поэтому вы должны прочесть его как только предложения.


Как @Денис упомянул, пауза может быть сделано очень просто и легко AsyncManualResetEvent но PauseToken/-источник, кажется, работает просто отлично.

4
ответ дан 6 апреля 2018 в 07:04 Источник Поделиться