我有一个名为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);
}
}
答案 0 :(得分:2)
通常,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
或类似技术),并在每批后检查暂停令牌