我已经设置了一堆Console.WriteLine
s,据我所知,当我在.NET Fiddle中运行以下内容时,没有一个被调用。
using System;
using System.Net;
using System.Linq.Expressions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Collections.Generic;
public class Program
{
private static readonly object locker = new object();
private static readonly string pageFormat = "http://www.letsrun.com/forum/forum.php?board=1&page={0}";
public static void Main()
{
var client = new WebClient();
// Queue up the requests we are going to make
var tasks = new Queue<Task<string>>(
Enumerable
.Repeat(0,50)
.Select(i => new Task<string>(() => client.DownloadString(string.Format(pageFormat,i))))
);
// Create set of 5 tasks which will be the at most 5
// requests we wait on
var runningTasks = new HashSet<Task<string>>();
for(int i = 0; i < 5; ++i)
{
runningTasks.Add(tasks.Dequeue());
}
var timer = new System.Timers.Timer
{
AutoReset = true,
Interval = 2000
};
// On each tick, go through the tasks that are supposed
// to have started running and if they have completed
// without error then store their result and run the
// next queued task if there is one. When we run out of
// any more tasks to run or wait for, stop the ticks.
timer.Elapsed += delegate
{
lock(locker)
{
foreach(var task in runningTasks)
{
if(task.IsCompleted)
{
if(!task.IsFaulted)
{
Console.WriteLine("Got a document: {0}",
task.Result.Substring(Math.Min(30, task.Result.Length)));
runningTasks.Remove(task);
if(tasks.Any())
{
runningTasks.Add(tasks.Dequeue());
}
}
else
{
Console.WriteLine("Uh-oh, task faulted, apparently");
}
}
else if(!task.Status.Equals(TaskStatus.Running)) // task not started
{
Console.WriteLine("About to start a task.");
task.Start();
}
else
{
Console.WriteLine("Apparently a task is running.");
}
}
if(!runningTasks.Any())
{
timer.Stop();
}
}
};
}
}
我也很欣赏有关如何简化或修复任何错误逻辑的建议。我试图做的模式就像
(1)创建N个任务的队列
(2)创建一组M个任务,第一个从(1)
出列的M队列(3)启动M任务运行
(4)X秒后,检查已完成的任务。
(5)对于任何已完成的任务,对结果执行某些操作,从集合中删除任务并将其替换为队列中的其他任务(如果队列中有任何任务)。
(6)无限期地重复(4) - (5)。
(7)如果该组没有任务,我们就完成了。
但也许有更好的方法来实现它,或者可能有一些.NET功能可以轻松地封装我尝试做的事情(Web请求并行指定的最大程度并行)。
答案 0 :(得分:3)
您的代码中存在多个问题,但由于您正在寻找更好的方法来实现它 - 您可以使用Parallel.For
或Parallel.ForEach
:
Parallel.For(0, 50, new ParallelOptions() { MaxDegreeOfParallelism = 5 }, (i) =>
{
// surround with try-catch
string result;
using (var client = new WebClient()) {
result = client.DownloadString(string.Format(pageFormat, i));
}
// do something with result
Console.WriteLine("Got a document: {0}", result.Substring(Math.Min(30, result.Length)));
});
它将并行执行主体(在任何给定时间不超过5个任务)。当一个任务完成时 - 下一个任务开始,直到它们全部完成,就像你想要的那样。
更新。使用这种方法有几种等待来限制任务,但最直接的只是睡眠:
Parallel.For(0, 50, new ParallelOptions() { MaxDegreeOfParallelism = 5 },
(i) =>
{
// surround with try-catch
var watch = Stopwatch.StartNew();
string result;
using (var client = new WebClient()) {
result = client.DownloadString(string.Format(pageFormat, i));
}
// do something with result
Console.WriteLine("Got a document: {0}", result.Substring(Math.Min(30, result.Length)));
watch.Stop();
var sleep = 2000 - watch.ElapsedMilliseconds;
if (sleep > 0)
Thread.Sleep((int)sleep);
});
答案 1 :(得分:2)
这不是您问题的直接答案。我只想提出一种替代方法。
我建议您考虑使用Microsoft的Reactive Framework(NuGet“System.Reactive”)来执行此类操作。
然后你可以这样做:
var query =
Observable
.Range(0, 50)
.Select(i => string.Format(pageFormat, i))
.Select(u => Observable.Using(
() => new WebClient(),
wc => Observable.Start(() => new { url = u, content = wc.DownloadString(u) })))
.Merge(5);
IDisposable subscription = query.Subscribe(x =>
{
Console.WriteLine(x.url);
Console.WriteLine(x.content);
});
所有异步都可以通过调用subscription.Dispose()
来随时停止该过程;