Question

I have been reading about the benefits of asynchronous programming. The advantages seem to be too great to continue to ignore it. Thus, I decided to make a determined foray into land of asyncs, Tasks, and awaits. I created a simple class to perform multiple concurrent POST operations to my Web Api.

Calling Method:

...

int tasks = 100;
for (var i = 0; i < tasks ; i++)
{
     int taskNumber = i + 1;
     var task = Task.Run(() => Post(taskNumber , address, client));
}

System.Console.ReadKey();

Task to be run:

private static async Task Post(int taskNumber, Uri address, HttpClient client)
{
        System.Console.WriteLine("Started Task#: {0}", taskNumber);
        Stopwatch sw = new Stopwatch();
        sw.Start();
        var status = GetDeviceStatus();

        var response = await client.PostAsJsonAsync<DeviceStatus>(address, status);
        sw.Stop();

        if (response.IsSuccessStatusCode)
        {
            Uri statusUri = response.Headers.Location;
            System.Console.WriteLine("{0}- Elapsed: {1} Path: {2}", taskNumber, sw.Elapsed, statusUri.ToString());
        }
        else
        {
            System.Console.WriteLine("{0}- Elapsed: {1} Path: {2}", taskNumber, sw.Elapsed, response.ToString());                
        }
    }

The code works as expected; however, is this the best way to perform this task (pardon the pun)? Any advice would be greatly appreciated.

Was it helpful?

Solution

I recommend you use TAP for I/O-based concurrency, and TPL for CPU-based concurrency. Since a WebAPI call is I/O-bound, you should be using TAP and not TPL.

There's no need to call Task.Run in this case:

int requests = 100;
for (var i = 0; i < requests; i++)
{
  int taskNumber = i + 1;
  var task = PostAsync(taskNumber, address, client);
}

You could also use Task.WhenAll for a TAP-based join:

int requests = 100;
var tasks = Enumerable.Range(0, requests).Select(x => PostAsync(x + 1, address, client));
await Task.WhenAll(tasks);

Also, HttpClient is mostly threadsafe. Details are on its MSDN page.

OTHER TIPS

The approach I would take is to use the Microsoft Reactive Framework for this kind of parallel code rather than tasks.

Here's what I'd do:

var query =
    from n in Observable.Range(0, 100)
    from r in Observable.Using(
        () => new HttpClient(),
        client =>
            Observable
                .Start(() =>
                {
                    var status = GetDeviceStatus();
                    return new
                    {
                        Response = client
                            .PostAsJson<DeviceStatus>(address, status),
                        Status = status,
                    };
                }))
    select new
    {
        Number = n,
        r.Response,
        r.Status,
    };

As this is a query it doesn't get run until you call the query in some way. Here's how:

query.Subscribe(x =>
{
    /* Do something with
        x.Number,
        x.Response,
        x.Status,
    */
});

This is all handled on the thread pool. All of the synchronization is done for you. And I can get all of the results neatly returned as an anonymous object.

There is no perfect way to do this kind of thing but I like this approach.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top