2

I'm building a solution to find a desired value from an API call inside a for loop.

I basically need to pass to an API the index of a for loop, one of those index will return a desired output, in that moment I want the for loop to break, but I need to efficient this process. I thought making it asynchronous without the await, so that when some API returns the desired output it breaks the loop.

Each API call takes around 10sec, so if I make this async or multithread I would reduce the execution time considerably.

I haven't fount any good orientation of how making async / not await HTTP request.

Any suggestions?

for (int i = 0; i < 60000; i += 256)
{            
    Console.WriteLine("Incrementing_value: " + i);
    string response = await client.GetStringAsync(
        "http://localhost:7075/api/Function1?index=" + i.ToString());
    Console.WriteLine(response);                
    if (response != "null")//
    {
        //found the desired output
        break;
    }
}
2
  • Do you want to launch the asynchronous operations concurrently, but impose a limit to the concurrency of these operations? Commented Jun 18, 2021 at 1:39
  • IMHO write an async method for the loop body, use a SemaphoreSlim to limit concurrency, create a CancellationTokenSource and cancel it when you find what you're looking for? (edit, that looks almost like the answer that just appeared.... ) Commented Jun 18, 2021 at 1:48

2 Answers 2

1

You can run requests in parallel, and cancel them once you have found your desired output:

public class Program {

    public static async Task Main() {
        var cts = new CancellationTokenSource();
        var client = new HttpClient();

        var tasks = new List<Task>();

        // This is the number of requests that you want to run in parallel.
        const int batchSize = 10;

        int requestId = 0;
        int batchRequestCount = 0;

        while (requestId < 60000) {
            if (batchRequestCount == batchSize) {
                // Batch size reached, wait for current requests to finish.
                await Task.WhenAll(tasks);
                tasks.Clear();
                batchRequestCount = 0;
            }

            tasks.Add(MakeRequestAsync(client, requestId, cts));
            requestId += 256;
            batchRequestCount++;
        }

        if (tasks.Count > 0) {
            // Await any remaining tasks
            await Task.WhenAll(tasks);
        }
    }

    private static async Task MakeRequestAsync(HttpClient client, int index, CancellationTokenSource cts) {
        if (cts.IsCancellationRequested) {
            // The desired output was already found, no need for any more requests.
            return;
        }

        string response;

        try {
            response = await client.GetStringAsync(
                "http://localhost:7075/api/Function1?index=" + index.ToString(), cts.Token);
        }
        catch (TaskCanceledException) {
            // Operation was cancelled.
            return;
        }

        if (response != "null") {
            // Cancel all current connections
            cts.Cancel();

            // Do something with the output ...
        }
    }

}

Note that this solution uses a simple mechanism to limit the amount of concurrent requests, a more advanced solution would make use of semaphores (as mentioned in some of the comments).

Sign up to request clarification or add additional context in comments.

2 Comments

Found and error on response = await client.GetStringAsync( "localhost:7075/api/Function1?index=" + index.ToString(), cts.Token); -> ERROR -> (awaitable) TASK<string>HttpClient.GetStringAsync(string requestUri) (+1 overload) Send Get request to the specified Uri and return the response body as string in an asynchronous operation. Returns: the task object representing the asynchronous operation. Exceptions: ArgumentNullException HttpRequestException CS1501: No overload for method 'GetStringAsync' takes 2 arguments.
GetStringAsync with CancellationToken is only available in .NET 5+. If you are using an earlier version, you can use HttpClient.GetAsync.
1

There are multiple ways to solve this problem. My personal favorite is to use an ActionBlock<T> from the TPL Dataflow library as a processing engine. This component invokes a provided Action<T> delegate for every data element received, and can also be provided with an asynchronous delegate (Func<T, Task>). It has many useful features, including (among others) configurable degree of parallelism/concurrency, and cancellation via a CancellationToken. Here is an implementation that takes advantage of those features:

async Task<string> GetStuffAsync()
{
    var client = new HttpClient();
    var cts = new CancellationTokenSource();
    string output = null;

    // Define the dataflow block
    var block = new ActionBlock<string>(async url =>
    {
        string response = await client.GetStringAsync(url, cts.Token);
        Console.WriteLine($"{url} => {response}");
        if (response != "null")
        {
            // Found the desired output
            output = response;
            cts.Cancel();
        }
    }, new ExecutionDataflowBlockOptions()
    {
        CancellationToken = cts.Token,
        MaxDegreeOfParallelism = 10 // Configure this to a desirable value
    });

    // Feed the block with URLs
    for (int i = 0; i < 60000; i += 256)
    {
        block.Post("http://localhost:7075/api/Function1?index=" + i.ToString());
    }
    block.Complete();

    // Wait for the completion of the block
    try { await block.Completion; }
    catch (OperationCanceledException) { } // Ignore cancellation errors
    return output;
}

The TPL Dataflow library is built-in the .NET Core / .NET 5. and it is available as a package for .NET Framework.

The upcoming .NET 6 will feature a new API Parallel.ForEachAsync, that could also be used to solve this problem in a similar fashion.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.