3

I'm trying to figure out if using aysnc/await will help application throughput when using HttpClient to POST to an external api.

Scenario: I have a class that POST's data to a payment processors web api. There are 4 steps to POST a payment: 1 - POST Contact 2 - POST Transaction 3 - POST Donation 4 - POST Credit Card Payment

Steps 1 - 4 must be sequential in order specified above.

My application does not have any "busy work" to do when waiting for a response from the payment processor - in this scenario does using async/await for the operations below make sense? Will it increase application throughput during high volume? Thanks!

Edit: (question was marked as not clear) 1. My application is a web api (microservice) 2. I'm using .Result (blocking) to avoid async/await (clearly this is wrong!) 3. We will have "spike" loads of 1000 req/minute

    public virtual ConstituentResponse PostConstituent(Constituent constituent)
    {
        var response =  PostToUrl<Constituent>("/api/Constituents", constituent);
        if (!response.IsSuccessStatusCode)
            HandleError(response);

        return response.Content.ReadAsAsync<ConstituentResponse>().Result;
    }

    public virtual TransactionResponse PostTransaction(Transaction transaction)
    {
        var response = PostToUrl<Transaction>("/api/Transactions", transaction);
        if (!response.IsSuccessStatusCode)
            HandleError(response);

        return response.Content.ReadAsAsync<TransactionResponse>().Result;
    }

    public virtual DonationResponse PostDonation(Donation donation)
    {
        var response = PostToUrl<Donation>("/api/Donations", donation);
        if (!response.IsSuccessStatusCode)
            HandleError(response);

        return response.Content.ReadAsAsync<DonationResponse>().Result;
    }

    public virtual CreditCardPaymentResponse PostCreditCardPayment(CreditCardPayment creditCardPayment)
    {
        var response = PostToUrl<CreditCardPayment>("/api/CreditCardPayments", creditCardPayment);
        if (!response.IsSuccessStatusCode)
            HandleError(response);

        return response.Content.ReadAsAsync<CreditCardPaymentResponse>().Result;
    }

    protected virtual HttpResponseMessage PostToUrl<T>(string methodUri, T value)
    {
        return _httpClient.PostAsJsonAsync(methodUri, value).Result;
    }

The five methods above are called from another class/function:

public virtual IPaymentResult Purchase(IDonationEntity donation, ICreditCard creditCard)
    {

        var constituentResponse = PostConstituent(donation);
        var transactionResponse = PostTransaction(donation, constituentResponse);
        var donationResponse = PostDonation(donation, constituentResponse, transactionResponse);
        var creditCardPaymentResponse = PostCreditCardPayment(donation, creditCard, transactionResponse);

        var paymentResult = new PaymentResult
        {
            Success = (creditCardPaymentResponse.Status == Constants.PaymentResult.Succeeded),
            ExternalPaymentID = creditCardPaymentResponse.PaymentID,
            ErrorMessage = creditCardPaymentResponse.ErrorMessage
        };

        return paymentResult;
    }
svick
  • 236,525
  • 50
  • 385
  • 514
  • Your question is a bit unclear - if your application has no "busy work" to do while waiting for a response, then where are you looking for performance gains? You need to show the way in which `Purchase` is called in order to determine whether `async` will help - if you're calling `Purchase` serially from a single thread, then multithreading will help, but if `Purchase` is being called from different threads (i.e. from your web server), I don't think it'll help, as your `...Async().Result` blocking calls will allow other threads to run while waiting for a result (as @usr pointed out) – Simon MᶜKenzie Dec 17 '15 at 00:37
  • There's probably not much point running experiments into _"if using aysnc/await will help application throughput"_ when you are not using `async` correctly. Stop calling `.Result()` for one –  Dec 17 '15 at 01:39
  • @user2521118 I have posted an answer, and I hope that I explained it well enough as well as correctly addressing your concerns. If not, please let me know. – David Pine Dec 17 '15 at 14:04

3 Answers3

2

You cannot actually utilize await Task.WhenAll here as when you are purchasing the next asynchronous operation relies on the result from the previous. As such you need to have them execute in the serialized manner. However, it is still highly recommended that you use async / await for I/O such as this, i.e.; web service calls.

The code is written with the consumption of Async* method calls, but instead of actually using the pattern -- it is blocking and could be a potential for deadlocks as well as undesired performance implications. you should only ever use .Result (and .Wait()) in console applications. Ideally, you should be using async / await. Here is the proper way to adjust the code.

public virtual async Task<ConstituentResponse> PostConstituenAsync(Constituent constituent)
{
    var response = await PostToUrlAsync<Constituent>("/api/Constituents", constituent);
    if (!response.IsSuccessStatusCode)
        HandleError(response);

    return await response.Content.ReadAsAsync<ConstituentResponse>();
}

public virtual async Task<TransactionResponse PostTransactionAsync(Transaction transaction)
{
    var response = await PostToUrl<Transaction>("/api/Transactions", transaction);
    if (!response.IsSuccessStatusCode)
        HandleError(response);

    return await response.Content.ReadAsAsync<TransactionResponse>();
}

public virtual async Task<DonationResponse> PostDonationAsync(Donation donation)
{
    var response = await PostToUrl<Donation>("/api/Donations", donation);
    if (!response.IsSuccessStatusCode)
        HandleError(response);

    return await response.Content.ReadAsAsync<DonationResponse>();
}

public virtual async Task<CreditCardPaymentResponse> PostCreditCardPaymentAsync(CreditCardPayment creditCardPayment)
{
    var response = await PostToUrlAsync<CreditCardPayment>("/api/CreditCardPayments", creditCardPayment);
    if (!response.IsSuccessStatusCode)
        HandleError(response);

    return await response.Content.ReadAsAsync<CreditCardPaymentResponse>();
}

protected virtual Task<HttpResponseMessage> PostToUrlAsync<T>(string methodUri, T value)
{
    return _httpClient.PostAsJsonAsync(methodUri, value);
}

Usage

public virtual await Task<IPaymentResult> PurchaseAsync(IDonationEntity donation, ICreditCard creditCard)
{
    var constituentResponse = await PostConstituentAsync(donation);
    var transactionResponse = await PostTransactionAsync(donation, constituentResponse);
    var donationResponse = await PostDonationAsync(donation, constituentResponse, transactionResponse);
    var creditCardPaymentResponse = await PostCreditCardPaymentAsync(donation, creditCard, transactionResponse);

    var paymentResult = new PaymentResult
    {
        Success = (creditCardPaymentResponse.Status == Constants.PaymentResult.Succeeded),
        ExternalPaymentID = creditCardPaymentResponse.PaymentID,
        ErrorMessage = creditCardPaymentResponse.ErrorMessage
    };

    return paymentResult;
}
David Pine
  • 23,787
  • 10
  • 79
  • 107
  • good point! async/await is really a pattern and the code should be modified from the request level / controller level to be async.. – user2521118 Dec 23 '15 at 01:56
1

First of all the way the code is written now does not help at all because you are blocking all the time by calling Result. If this was a good thing to do, why wouldn't all APIs simply do this internally for you?! You can't cheat with async...

You will only see throughput gains if you exceed the capabilities of the thread pool which happens in the 100s of threads range.

he average number of threads needed is requestsPerSecond * requestDurationInSeconds. Plug in some numbers to see whether this is realistic for you.

I'll link you my standard posts on whether to go sync or async because I feel you don't have absolute clarity for when async IO is appropriate.

https://stackoverflow.com/a/25087273/122718 Why does the EF 6 tutorial use asychronous calls? https://stackoverflow.com/a/12796711/122718 Should we switch to use async I/O by default?

Generally, it is appropriate when the wait times are long and there are many parallel requests running.

My application does not have any "busy work" to do when waiting for a response

The other requests coming in are such busy work.

Note, that when a thread is blocked the CPU is not blocked as well. Another thread can run.

Community
  • 1
  • 1
usr
  • 168,620
  • 35
  • 240
  • 369
  • Yes the other requests coming in matter which is the reason I'm concerned about .Result and trying to figure out how to use async/await. If the POST creditcard takes 2 seconds, then will the current thread be freed up to handle other requests? (using async/await) – user2521118 Dec 17 '15 at 01:15
  • Yes, use async and await for everything. – usr Dec 17 '15 at 14:34
-1

When you are doing async/await, you should async all the day. Read Async/Await - Best Practices in Asynchronous Programming

You need to make them return async

 public virtual async Task ConstituentResponse PostConstituent(Constituent constituent)
{
    var response =  PostToUrl<Constituent>("/api/Constituents", constituent);
    if (!response.IsSuccessStatusCode)
        HandleError(response);

    return await response.Content.ReadAsAsync<ConstituentResponse>();
}
//...
//etc

And then from the main function

  await Task.WhenAll(constituentResponse, transactionResponse, donationResponse, creditCardPaymentResponse);

Edit: Misread OP. Don't use await Task.WhenAll for synchronous calls

joordan831
  • 720
  • 5
  • 6
  • Interesting.. does Task.WhenAll execute the 4 calls sequentially because the calls are dependent on data from a call in the previous step.. – user2521118 Dec 17 '15 at 01:14
  • Fine answer, but yes since OP's methods depend on each other, `Task.WhenAll()` is arguably not suitable here –  Dec 17 '15 at 01:43
  • @Micky Yea, you're right. I had overlooked the OP. Yes, they would have to be executed sequentially and there's no real benefit of doing them in async way. – joordan831 Dec 17 '15 at 02:03
  • Joordan, good point. @user2521118 it's not clear what benefit you would get from the point of view of the client waiting on a series of dependent and consecutive "async" calls –  Dec 17 '15 at 02:12
  • @Micky and joordan831 - that was my thought as well when I wrote the code. The word "blocking" scares me but I ended up using the HttpClient class because it makes it easy to serialize business objects to/from JSON.. is it safe to assume .Result is the same as synchronous code? As long as it's not blocking other threads it's all good.. we will scale with more hardware / load balancing to handle high volumes. – user2521118 Dec 17 '15 at 02:42
  • Looking at it again, you can convert last two method calls, PostDonation and PostCreditCardPayment, into async and wait for the task to finish before you new PaymentResult. Or if performance is of a big concern, since they all need to sequentially happen (and in a single transaction), you can also consider combining methods by processing them over at the server side. For example, move all 4 methods over to the server side and from the client just do a single post. – joordan831 Dec 17 '15 at 08:28
  • I posted an answer. I believe that you cannot use `await Task.WhenAll` as described in my answer. The code requires serialized execution... – David Pine Dec 17 '15 at 13:52