Parallel.ForEach using Thread.Sleep equivalent

后端 未结 2 531
孤独总比滥情好
孤独总比滥情好 2021-01-18 07:40

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 searc

相关标签:
2条回答
  • 2021-01-18 08:06

    It's quite easy to implement with tasks and async/await, as noted by @KevinS in the comments:

    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);
    

    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 with async/await, the async-await tag wiki might be a good place to start.

    I assume you can convert your synchronous methods isSearchFinished and downloadData to asynchronous versions using something like HttpClient for non-blocking HTTP request and returning a Task<>. If you are unable to do so, you still can simply wrap them with Task.Run, as await Task.Run(() => isSearchFinished(item)) and await 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 with Parallel.ForEach in this case, because you won't be blocking pool threads for 30s, thanks to asynchronous Task.Delay.

    0 讨论(0)
  • 2021-01-18 08:06

    You can also write a generic function using TaskCompletionSource and Threading.Timer to return a Task that becomes complete once a specified retry function succeeds.

    public static Task RetryAsync(Func<bool> retryFunc, TimeSpan retryInterval)
    {
        return RetryAsync(retryFunc, retryInterval, CancellationToken.None);
    }
    
    public static Task RetryAsync(Func<bool> retryFunc, TimeSpan retryInterval, CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<object>();
    
        cancellationToken.Register(() => tcs.TrySetCanceled());
    
        var timer = new Timer((state) =>
        {
            var taskCompletionSource = (TaskCompletionSource<object>) state;
    
            try
            {                   
                if (retryFunc())
                {
                    taskCompletionSource.TrySetResult(null);
                }
            }
            catch (Exception ex)
            {
                taskCompletionSource.TrySetException(ex);
            }
        }, tcs, TimeSpan.FromMilliseconds(0), retryInterval);
    
        // Once the task is complete, dispose of the timer so it doesn't keep firing. Also captures the timer
        // in a closure so it does not get disposed.
        tcs.Task.ContinueWith(t => timer.Dispose(),
                              CancellationToken.None,
                              TaskContinuationOptions.ExecuteSynchronously,
                              TaskScheduler.Default);
    
        return tcs.Task;
    }
    

    You can then use RetryAsync like this:

    var searchTasks = new List<Task>();
    
    searchTasks.AddRange(items.Select(
            downloadItem => RetryAsync( () => isSearchFinished(downloadItem),  TimeSpan.FromSeconds(2))  // retry timout
            .ContinueWith(t => downloadData(downloadItem), 
                          CancellationToken.None, 
                          TaskContinuationOptions.OnlyOnRanToCompletion, 
                          TaskScheduler.Default)));
    
    await Task.WhenAll(searchTasks.ToArray());
    

    The ContinueWith part specifies what you do once the task has completed successfully. In this case it will run your downloadData method on a thread pool thread because we specified TaskScheduler.Default and the continuation will only execute if the task ran to completion, i.e. it was not canceled and no exception was thrown.

    0 讨论(0)
提交回复
热议问题