JustLogin
JustLogin

Reputation: 1886

Make the code asynchronous in C#

I have 2 methods: the first sends HTTP GET request on one address and the second calls it multiple times (so, it sends request to many IPs). Both methods are async, so they don't block code execution while the requests are proccessing remotely. The problem is, due to my poor C# knowledge, I don't know how to send all the requests simultaneously, not one after another (which my code does). That's my code:

public static async Task<string> SendRequest(Uri uri)
{
    using (var client = new HttpClient())
    {
        var resp = await client.GetStringAsync(uri).ConfigureAwait(false);
        return resp;
    }
}

public static async Task<string[]> SendToAllIps(string req)
{
    string[] resp = new string[_allIps.Length];

    for (int i = 0; i < _allIps.Length; i++)
    {
        resp[i] = await SendRequest(new Uri(_allIps[i] + req));
    }

    return resp;
}

How to make SendToAllIps send requests without awaiting for previous task result? Also SendToAllIps must return an array of responses when all the requests are finished. As far as I understand, this can be done with Task.WaitAll, but how to use it in this particular situation?

Upvotes: 1

Views: 164

Answers (3)

smoksnes
smoksnes

Reputation: 10871

public static async Task<string[]> SendToAllIps(string req)
{
    var tasks = new List<Task<string>>();

    for (int i = 0; i < _allIps.Length; i++)
    {
        // Start task and assign the task itself to a collection.
        var task = SendRequest(new Uri(_allIps[i] + req));
        tasks.Add(task);
    }

    // await all the tasks.
    string[] resp = await Task.WhenAll(tasks);
    return resp;
}

The key here is to collect all the tasks in a collection and then await them all using await Task.WhenAll. Even though I think the solution from Lee is more elegant...

Upvotes: 1

Mrinal Kamboj
Mrinal Kamboj

Reputation: 11478

Answers provided above provide the correct way of doing it but doesn't provide rationale, let me explain what's wrong with your code:

Following line creates an issue:

resp[i] = await SendRequest(new Uri(_allIps[i] + req));

Why ?

As you are awaiting each individual request, it will stop the processing of the remaining requests, that's the behavior of the async-await and it will be almost the synchronous processing of each SendRequest when you wanted then to be concurrent.

Resolution: SendRequest being an async method returns as Task<string>, you need add that to an IEnumerable<Task<string>> and then you have option:

Task.WaitAll or Task.WhenAll

Yours is Web API (Rest Application), which needs a Synchronization Context, so you need Task.WhenAll, which provides a Task as result to wait upon and integrate all the task results in an array, if you try using Task.WaitAll it will lead to deadlock, as it is not able to search the Synchronization Context, check the following:

WaitAll vs WhenAll

You can use the Task.WaitAll only for the Console application not the Web Application, Console App doesn't need any Synchronization Context or UI Thread

Upvotes: 1

Lee
Lee

Reputation: 144206

You can use Task.WhenAll to await a collection of tasks:

public static async Task<string[]> SendToAllIps(string req)
{
    var tasks = _allIps.Select(ip => SendRequest(new Uri(ip + req)));
    return await Task.WhenAll(tasks);
}

Upvotes: 5

Related Questions