So here's the situation: I need to make a call to a web site that starts a search. This search continues for an unknown amount of time, and the only way I know if the search has finished is by periodically querying the website to see if there's a "Download Data" link somewhere on it (it uses some strange ajax call on a javascript timer to check the backend and update the page, I think).
So here's the trick: I have hundreds of items I need to search for, one at a time. So I have some code that looks a little bit like this:
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);
}
Now, obviously this isn't the real code, because there could be things that cause isSearchFinished
to always be false
.
Obvious infinite loop danger aside, how would I correctly keep isSearchFinished()
from calling over and over and over, but instead call every, say, 30 seconds or 1 minute?
I know Thread.Sleep()
isn't the right solution, and I think the solution might be accomplished by using Threading.Timer()
but I'm not very familiar with it, and there are so many threading options that I'm just not sure which to use.
You can also write a generic function using
TaskCompletionSource
andThreading.Timer
to return aTask
that becomes complete once a specified retry function succeeds.You can then use
RetryAsync
like this:The
ContinueWith
part specifies what you do once the task has completed successfully. In this case it will run yourdownloadData
method on a thread pool thread because we specifiedTaskScheduler.Default
and the continuation will only execute if the task ran to completion, i.e. it was not canceled and no exception was thrown.It's quite easy to implement with tasks and
async/await
, as noted by @KevinS in the comments:This way, you don't block
ThreadPool
threads in vain for what is mostly a bunch of I/O-bound network operations. If you're not familiar withasync/await
, theasync-await
tag wiki might be a good place to start.I assume you can convert your synchronous methods
isSearchFinished
anddownloadData
to asynchronous versions using something likeHttpClient
for non-blocking HTTP request and returning aTask<>
. If you are unable to do so, you still can simply wrap them withTask.Run
, asawait Task.Run(() => isSearchFinished(item))
andawait Task.Run(() => downloadData(item))
. Normally this is not recommended, but as you have hundreds of items, it sill would give you a much better level of concurrency than withParallel.ForEach
in this case, because you won't be blocking pool threads for 30s, thanks to asynchronousTask.Delay
.