我正在使用.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);
}
答案 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);
}