所以情况就是这样:我需要拨打一个开始搜索的网站.这种搜索会持续一段时间,并且我知道搜索完成的唯一方法是定期查询网站以查看其上是否存在“下载数据”链接(它在
javascript上使用了一些奇怪的ajax调用)计时器检查后端并更新页面,我认为).
所以这就是诀窍:我需要搜索数百个项目,一次一个.所以我有一些看起来有点像这样的代码:
var items = getItems(); Parallel.ForEach(items,item => { startSearch(item); var finished = isSearchFinished(item); while(finished == false) { finished = isSearchFinished(item); //<--- How do I delay this action 30 Secs? } downloadData(item); }
现在,显然这不是真正的代码,因为可能会导致isSearchFinished始终为false.
除了明显的无限循环危险之外,我如何正确地保持isSearchFinished()一次又一次地调用,而是调用每一个,比如30秒或1分钟?
我知道Thread.Sleep()不是正确的解决方案,我认为解决方案可能是通过使用Threading.Timer()完成的,但我对它并不是很熟悉,而且我有很多线程选项我都是只是不确定使用哪个.
解决方法
正如@KevinS在评论中指出的那样,使用任务和async / await很容易实现:
async Task<ItemData> ProcessItemAsync(Item item) { while (true) { if (await isSearchFinishedAsync(item)) break; await Task.Delay(30 * 1000); } return await downloadDataAsync(item); } // ... var items = getItems(); var tasks = items.Select(i => ProcessItemAsync(i)).ToArray(); await Task.WhenAll(tasks); var data = tasks.Select(t = > t.Result);
这样,您不会因为大多数I / O绑定的网络操作而无法阻塞ThreadPool线程.如果你不熟悉async / await,那么async-await
标签wiki可能是一个很好的起点.
我假设您可以将同步方法isSearchFinished和downloadData转换为异步版本,使用HttpClient等非阻塞HTTP请求并返回Task<>.如果你不能这样做,你仍然可以简单地用Task.Run包装它们,等待Task.Run(()=> isSearchFinished(item))并等待Task.Run(()=> downloadData(item) ).通常不建议这样做,但是因为你有数百个项目,所以在这种情况下,它会比使用Parallel.ForEach更好的并发性,因为你不会阻塞池线程30s,这要归功于异步任务.延迟.