Using async/await or task in web api controller (.net core)
This works well however I am wondering if using tasks is the best solution here? Would using async/await be a better idea and more accepted way?
Yes, absolutely. Doing parallel processing on ASP.NET consumes multiple threads per request, which can severely impact your scalability. Asynchronous processing is far superior for I/O.
To use async
, first start with your lowest-level call, somewhere inside your service. It's probably doing an HTTP call at some point; change that to use asynchronous HTTP calls (e.g., HttpClient
). Then let async
grow naturally from there.
Eventually, you'll end up with asynchronous getdata1Async
, getdata2Async
, and getdata3Async
methods, which can be consumed concurrently as such:
[HttpGet]
public async Task<IActionResult> myControllerAction()
{
var t1 = service.getdata1Async();
var t2 = service.getdata2Async();
var t3 = service.getdata3Async();
await Task.WhenAll(t1, t2, t3);
var data = new returnObject
{
d1 = await t1,
d2 = await t2,
d3 = await t3
};
return Ok(data);
}
With this approach, while the three service calls are in progress, myControllerAction
uses zero threads instead of four.
As i understand, you want this to execute in parallel, so I don't think there is anything wrong with your code. As Gabriel mentioned, you could await the the finishing of the tasks.
[HttpGet]
public async Task<IActionResult> myControllerAction()
{
var data1 = new sometype1();
var data2 = new sometype2();
var data3 = new List<sometype3>();
var t1 = Task.Run(() => { data1 = service.getdata1(); });
var t2 = Task.Run(() => { data2 = service.getdata2(); });
var t3 = Task.Run(() => { data3 = service.getdata3(); });
await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here
var data = new returnObject
{
d1 = data1,
d2 = data2,
d2 = data3
};
return Ok(data);
}
You could also use the results of the tasks to save some lines of codes and make the code overall "better" (see comments):
[HttpGet]
public async Task<IActionResult> myControllerAction()
{
var t1 = Task.Run(() => service.getdata1() );
var t2 = Task.Run(() => service.getdata2() );
var t3 = Task.Run(() => service.getdata3() );
await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here
var data = new returnObject
{
d1 = t1.Result,
d2 = t2.Result,
d2 = t3.Result
};
return Ok(data);
}
[HttpGet]
public async Task<IActionResult> GetAsync()
{
var t1 = Task.Run(() => service.getdata1());
var t2 = Task.Run(() => service.getdata2());
var t3 = Task.Run(() => service.getdata3());
await Task.WhenAll(t1, t2, t3);
var data = new returnObject
{
d1 = t1.Status == TaskStatus.RanToCompletion ? t1.Result : null,
d2 = t2.Status == TaskStatus.RanToCompletion ? t2.Result : null,
d3 = t3.Status == TaskStatus.RanToCompletion ? t3.Result : null
};
return Ok(data);
}
- Your action thread is currently blocked when you are waiting for tasks. Use
TaskWhenAll
to return awaitable Task object. Thus with async method you can await for tasks instead of blocking thread. - Instead of creating local variables and assigning them in tasks, you can use
Task<T>
to return results of required type. - Instead of creating and running tasks, use
Task<TResult>.Run
method - I recommend to use convention for action names - if action accepts GET request, it's name should starts with
Get
- Next, you should check whether tasks completed successfully. It is done by checking task status. In my sample I used
null
values for return object properties if some of tasks do not complete successfully. You can use another approach - e.g. return error if some of tasks failed.