Kumar
Kumar

Reputation: 307

Convert conventional method to Asynchronous

I have an API which is responsible for inserting text message details in database. It does by making synchronous call to repository which I think could be implemented asynchronous way.How can I achieve this? Or what could be the best way to handle this scenario.Code snippet example is highly appreciated as I am still getting my ahead wrapping around .NET.

api:

public IHttpActionResult SendSMSNotification([FromBody] SMSNotification smsNotification)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }            
        _service.SendSMS(smsNotification);

        return Ok();
    }

Service:

internal void SendSMS(SMSNotification smsNotification)
    {
        _repository.Notify(_mapperService.GetSMSNotification(smsNotification));
    }

mapper:

public SMSNotification GetSMSNotification(SMSNotification message)
    {
        return AutoMapper.Mapper.Map<SMSNotification>(message); 
    }

repo:

public virtual bool Notify(SMSNotification request)
    {
        using (var sql = _sqlMapper.CreateCommand('Database', 'Stored proc'))
        {
            sql.AddParam("@fMessage", request.Message);
           //..............
           //.............. more params
            var retvalParamOutput = sql.AddOutputParam("@fRetVal", System.Data.SqlDbType.Int);
            sql.Execute();
            return retvalParamOutput.GetSafeValue<int>() == 1;
        }
    }

sql here is a custom thing and it has following methods:

  public static int Execute(this IDataCommand @this);
        [AsyncStateMachine(typeof(<ExecuteAsync>d__1))]
        public static Task<int> ExecuteAsync(this IDataCommand @this);

Upvotes: 0

Views: 277

Answers (1)

David Moore
David Moore

Reputation: 2526

Changing a blocking, typically IO-bound call (such as database, network or file system work) to async can make your app scale better.

This does have a flow-on affect through your API. That is, you need to be awaiting on asynchronous calls all the way up to the top-most call, otherwise, somewhere is going to block and you're just lost the benefit of calling an async API.

To demonstrate that, let's start from the bottom at the repository call, as that's the possibly expensive blocking operation can be made async. We alter sql.Execute to use the asynchronous version ExecutAsync version instead:

repo:

public virtual async Task<bool> Notify(SMSNotification request)
{
    using (var sql = _sqlMapper.CreateCommand('Database', 'Stored proc'))
    {
        sql.AddParam("@fMessage", request.Message);
       //..............
       //.............. more params
        var retvalParamOutput = sql.AddOutputParam("@fRetVal", System.Data.SqlDbType.Int);
        await sql.ExecuteAsync();
        return retvalParamOutput.GetSafeValue<int>() == 1;
    }
}

Now here we have to change the signature of the method to return a Task wrapping a result of bool.

We also mark the method as async, so then we can use the "await" operator further down. Without doing this, we'd have to do more refactoring to manipulate and return the Task result ourselves, but the "async" modifier and "await" keyword let the compiler do that magic for us, and the rest of our code mostly looks like normal.

The mapper call doesn't really need to change:

mapper:

public SMSNotification GetSMSNotification(SMSNotification message)
{
    return AutoMapper.Mapper.Map<SMSNotification>(message); 
}

The service call is now making a call to an async method, so because we want to await and not block on that async call, we have to also change this previously void method to an async method. Note we change it from "void" to "async Task"; you CAN mark a void method as "async void", but that's intended as a workaround for event handlers in Windows Forms and WPF apps; in every other case you want to change a "void" method to "async Task" when making it async.

Service:

internal async Task SendSMS(SMSNotification smsNotification)
{
    await _repository.Notify(_mapperService.GetSMSNotification(smsNotification));
}

Then finally, our API call can be made async, and await our service call:

api:

public async Task<IHttpActionResult> SendSMSNotification([FromBody] SMSNotification smsNotification)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }            
    await _service.SendSMS(smsNotification);

    return Ok();
}

It's sometimes recommended that after you do a refactor like this, to rename the methods to end in "Async" by convention; however I don't think this is really compulsory, as so much of the .NET API surface is becoming async, it's almost redundant now.

It's worth getting your head around the async / await stuff though; I've tried to keep the example relatively brief. But I hope this at least gets you started.

Upvotes: 2

Related Questions