使用任务并行库可以处理频繁的URL请求

时间:2014-12-20 20:02:20

标签: c# .net multithreading task-parallel-library

我正在使用.Net来构建股票报价更新程序。假设在市场营业时间内有更多X股票符号需要更新。为了使更新保持在不超过数据提供者限制的速度(例如Yahoo财务),我将尝试使用类似于线程池的机制来限制请求数/秒。让我们说我想只允许5个请求/秒,这对应于5个线程的池。

我听说过TPL并希望使用它,尽管我没有经验。如何在Task中指定隐式使用的池中的线程数?这是一个调度请求的循环,其中requestFunc(url)是更新引号的函数。我想从专家那里得到一些意见或建议:

// X is a number much bigger than 5
List<Task> tasks = new List<Task>();
for (int i=0; i<X; i++)
{
    Task t = Task.Factory.StartNew(() => { requestFunc(url); }, TaskCreationOptions.None);
    t.Wait(100); //slow down 100 ms. I am not sure if this is the right thing to do
    tasks.Add(t);
}

Task.WaitAll(tasks);

好的,我添加了一个外循环以使其连续运行。当我对@ steve16351的代码进行一些更改时,它只循环一次。为什么????

static void Main(string[] args)
    {
        LimitedExecutionRateTaskScheduler scheduler = new LimitedExecutionRateTaskScheduler(5);
        TaskFactory factory = new TaskFactory(scheduler);
        List<string> symbolsToCheck = new List<string>() { "GOOG", "AAPL", "MSFT", "AGIO", "MNK", "SPY", "EBAY", "INTC" };


        while (true)
        {
            List<Task> tasks = new List<Task>();
            Console.WriteLine("Starting...");

            foreach (string symbol in symbolsToCheck)
            {
                Task t = factory.StartNew(() => { write(symbol); },
                                                                     CancellationToken.None, TaskCreationOptions.None, scheduler);
                tasks.Add(t);
            }
            //Task.WhenAll(tasks);

            Console.WriteLine("Ending...");
            Console.Read();
        }

        //Console.Read();
    }

    public static void write (string symbol)
    {
        DateTime dateValue = DateTime.Now;
        //Console.WriteLine("[{0:HH:mm:ss}] Doing {1}..", DateTime.Now, symbol);
        Console.WriteLine("Date and Time with Milliseconds: {0} doing {1}..",
               dateValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"), symbol);
    }

5 个答案:

答案 0 :(得分:2)

如果您希望获得网址请求流,同时限制为不超过5个并发操作,则应使用TPL数据流&#39; ActionBlock

var block = new ActionBlock<string>(
    url => requestFunc(url),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5 });

foreach (var url in urls)
{
    block.Post(url);
}

block.Complete();
await block.Completion;

Post向其添加网址,并且每个网址都会执行请求,同时确保一次只有MaxDegreeOfParallelism个请求。

完成后,您可以调用Complete表示阻止完成,并await Completion {{1}}任务异步等待直到阻止实际完成。

答案 1 :(得分:1)

不要担心线程数量;只要确保你没有超过每秒的请求数。使用单个计时器每200毫秒发出ManualResetEvent信号,并让任务在循环内等待该ManualResetEvent。

创建一个计时器并使其每200毫秒发出一次ManualResetEvent信号:

resetEvent = new ManualResetEvent(false);
timer = new Timer((state)=>resetEvent.Set(), 200, 0);

确保在不再需要时清理定时器(调用Dispose)。

让线程数由运行时确定。

如果您为每个库存创建一个任务,这将是一个糟糕的实施,因为您不知道何时更新库存。

所以你可以把所有的股票放在一个列表中,然后让一个任务一个接一个地更新每个股票。

通过将另一个股票列表提供给另一个任务,您可以通过将其计时器设置为每250毫秒并将低优先级设置为每1000毫秒来为该任务赋予更高的优先级。这将加起来每秒5次,高优先级列表将比低优先级更新4倍。

答案 2 :(得分:0)

您可以使用带有任务延迟的while循环来控制何时发出请求。使用异步void方法发出请求意味着您不会被失败的请求阻止。

异步虚空是火,忘记了一些开发者不喜欢的东西,但我认为在这种情况下它可以作为一种可能的解决方案。

我也认为erno de weerd提出了一个很好的建议,可以优先考虑更重要的股票。

答案 3 :(得分:0)

您可以使用自定义任务计划程序来限制任务的启动速度。

在下面,任务排队等候,并将计时器设置为最大允许速率的频率。因此,如果5请求一秒钟,则定时器设置为200ms。在勾选时,一个任务然后从那些待处理的任务中出列并执行。

编辑:除了请求率之外,您还可以扩展以控制执行线程的最大数量。

static void Main(string[] args)
{
    TaskFactory factory = new TaskFactory(new LimitedExecutionRateTaskScheduler(5, 5)); // 5 per second, 5 max executing
    List<string> symbolsToCheck = new List<string>() { "GOOG", "AAPL", "MSFT" };

    for (int i = 0; i < 5; i++)
        symbolsToCheck.AddRange(symbolsToCheck);

    foreach (string symbol in symbolsToCheck)
    {
        factory.StartNew(() =>
        {
            Console.WriteLine("[{0:HH:mm:ss}] [{1}] Doing {2}..", DateTime.Now, Thread.CurrentThread.ManagedThreadId, symbol);
            Thread.Sleep(5000);
            Console.WriteLine("[{0:HH:mm:ss}] [{1}] {2} is done", DateTime.Now, Thread.CurrentThread.ManagedThreadId, symbol);
        });
    }

    Console.Read();
}


public class LimitedExecutionRateTaskScheduler : TaskScheduler
{
    private ConcurrentQueue<Task> _pendingTasks = new ConcurrentQueue<Task>();            
    private readonly object _taskLocker = new object();
    private List<Task> _executingTasks = new List<Task>();
    private readonly int _maximumConcurrencyLevel = 5;
    private Timer _doWork = null;

    public LimitedExecutionRateTaskScheduler(double requestsPerSecond, int maximumDegreeOfParallelism)
    {
        _maximumConcurrencyLevel = maximumDegreeOfParallelism;
        long frequency = (long)(1000.0 / requestsPerSecond);
        _doWork = new Timer(ExecuteRequests, null, frequency, frequency);
    }

    public override int MaximumConcurrencyLevel
    {
        get
        {
            return _maximumConcurrencyLevel;
        }
    }

    protected override bool TryDequeue(Task task)
    {
        return base.TryDequeue(task);
    }
    protected override void QueueTask(Task task)
    {
        _pendingTasks.Enqueue(task);
    }

    private void ExecuteRequests(object state)
    {
        Task queuedTask = null;
        int currentlyExecutingTasks = 0;

        lock (_taskLocker)
        {
            for (int i = 0; i < _executingTasks.Count; i++)
                if (_executingTasks[i].IsCompleted)
                    _executingTasks.RemoveAt(i--);

            currentlyExecutingTasks = _executingTasks.Count;
        }

        if (currentlyExecutingTasks == MaximumConcurrencyLevel)
            return;

        if (_pendingTasks.TryDequeue(out queuedTask) == false)
            return; // no work to do

        lock (_taskLocker)
            _executingTasks.Add(queuedTask);

        base.TryExecuteTask(queuedTask);
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return false; // not properly implemented just to complete the class
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return new List<Task>(); // not properly implemented just to complete the class
    }
}

答案 4 :(得分:0)

谢谢@ steve16351!它的工作原理如下:

    static void Main(string[] args)
    {
        LimitedExecutionRateTaskScheduler scheduler = new LimitedExecutionRateTaskScheduler(5);
        TaskFactory factory = new TaskFactory(scheduler);
        List<string> symbolsToCheck = new List<string>() { "GOOG", "AAPL", "MSFT", "AGIO", "MNK", "SPY", "EBAY", "INTC" };


        while (true)
        {
            List<Task> tasks = new List<Task>();
            foreach (string symbol in symbolsToCheck)
            {
                Task t = factory.StartNew(() => 
                {
                    write(symbol);
                }, CancellationToken.None, 
                   TaskCreationOptions.None, scheduler);
                tasks.Add(t);
            } 
        } 
    }

    public static void write (string symbol)
    {
        DateTime dateValue = DateTime.Now;
        Console.WriteLine("Date and Time with Milliseconds: {0} doing {1}..",
               dateValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"), symbol);
    }