2016-11-24 2 views
4

Я установил связку 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) Создание queueu из N задач

(2) Создать набор задач M, первый M из очереди элементы из (1)

(3) Запустите выполнение заданий M

(4) Через X секунд проверьте выполненные задачи.

(5) Для любой завершенной задачи сделайте что-нибудь с результатом, удалите задачу из набора и замените ее другой задачей из очереди (если она оставлена ​​в очереди).

(6) Повторите (4) - (5) на неопределенный срок.

(7) Если у набора нет заданий, мы закончили.

но, возможно, есть лучший способ реализовать его, или, возможно, есть какая-то функция .NET, которая легко инкапсулирует то, что я пытаюсь сделать (веб-запросы параллельно с указанной максимальной степенью параллелизма).

+0

Была ли причина, по которой вы выбрали этот подход, просто нажав задачи в ThreadPool? Не говоря о том, что вы ошибаетесь, мне просто интересно. –

+0

Простой ActionBlock с конкретным DOP решит проблему –

ответ

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); 
}); 
+0

Одна из причин, по которым я не хочу этого делать, - это то, что у нее есть потенциал для наводнения сайта запросами. Если я получу первые 5 ответов очень быстро, я все же хочу подождать секунду или две, прежде чем делать следующую партию запросов. – user7127000

+0

Вы знаете, как именно сайт ограничивает одновременный запрос? Я имею в виду, что-то вроде «не более X запросов в Y секунд». – Evk

+0

Нет, не знаю.Но давайте предположим, что я сделал: тогда какая была бы процедура с точки зрения X и Y? – user7127000

2

Это не является прямым ответом на ваш вопрос. Я просто хотел предложить альтернативный подход.

Я бы порекомендовал вам изучить использование 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();

+0

Не помешает добавить, как обрабатывать запросы в этом случае (например, автору необходимо, чтобы между каждой партией из 5 запросов прошло не менее двух секунд). – Evk

+0

@Evk Rx Это намного легче дросселировать с помощью Rx, у него уже есть операторы для дросселирования или буферизации событий. –

+0

@PanagiotisKanavos да, но так как это ответ, было бы хорошо описать, как именно это сделать в этой конкретной ситуации. – Evk

Смежные вопросы