user2521118
user2521118

Reputation: 33

async method for performance / throughput of web services

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

Upvotes: 3

Views: 710

Answers (3)

joordan831
joordan831

Reputation: 720

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

Upvotes: -1

David Pine
David Pine

Reputation: 24535

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

Upvotes: 2

usr
usr

Reputation: 171246

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.

Upvotes: 1

Related Questions