user7127000 user7127000 - 10 days ago 5
C# Question

How can I check whether this interval is even running?

I've set a bunch of

Console.WriteLine
s and as far as I can tell none of them are being invoked when I run the following in .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();
}
}

};
}
}


I'd also appreciate advice on how I can simplify or fix any faulty logic in this. The pattern I'm trying to do is like

(1) Create a queueu of N tasks

(2) Create a set of M tasks, the first M dequeued items from (1)

(3) Start the M tasks running

(4) After X seconds, check for completed tasks.

(5) For any completed task, do something with the result, remove the task from the set and replace it with another task from the queue (if any are left in the queueu).

(6) Repeat (4)-(5) indefinitely.

(7) If the set has no tasks left, we're done.

but perhaps there's a better way to implement it, or perhaps there's some .NET function that easily encapsulates what I'm trying to do (web requests in parallel with a specified max degree of parallelism).

Evk Evk
Answer

There are several issues in your code, but since you are looking for better way to implement it - you can use Parallel.For or Parallel.ForEach:

Parallel.For(0, 50, new ParallelOptions() {MaxDegreeOfParallelism = 5}, (i) =>
{
    // surround with try-catch
    var result = new WebClient().DownloadString(string.Format(pageFormat, i));
   // do something with result
   Console.WriteLine("Got a document: {0}", result.Substring(Math.Min(30, result.Length)));
});

It will execute the body in parallel (not more than 5 tasks at any given time). When one task is completed - next one is started, until they are all done, just like you want.

UPDATE. There are several waits to throttle tasks with this approach, but the most straightforward is just sleep:

Parallel.For(0, 50, new ParallelOptions() { MaxDegreeOfParallelism = 5 },  
(i) =>
{
    // surround with try-catch
    var watch = Stopwatch.StartNew();
    var result = new WebClient().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);
});
Comments