0

I am building my dotnet core webapi service for the point of PoC and I am concerned about setting a proper guidance for other archs/devs moving forward.

There are few variations for the "same" method signature

  1. public dynamic Get(string name = _defaultName)
  2. public IActionResult Get(string name = _defaultName)
  3. public async Task Get(string name = _defaultName)

Leaving discussion alone on why I am using dynamic instead of custom type, I am trying to understand the differences and benefits.

Please correct my (mis)understanding:

  1. No control over http response, headers, code, etc - all set by the framework in case of success or failure, return type is visible to run-time tools
  2. Better control over the http response, but it will tie up the threat while executing the method, no definition for the output data type
  3. Better control over http response, will not tie up the threat while executing the method, but will spin off another thread to compute the result, and no definition for the output type

The implementation for the 3rd option will look like following:

public async Task<IActionResult> Get(string name = _defaultName)
    {
        return await Task.Run(() => {
            dynamic response = new System.Dynamic.ExpandoObject();
            response.message = $"Hello {name}!";
            response.timestamp = DateTime.Now.ToUniversalTime();

            if (name.Equals(_defaultName, StringComparison.CurrentCultureIgnoreCase)) {
                response.hint = "Add forward slash and your name to the url, e.g.: hostname:port/my name";
            }

            return Ok(response);
        });
    }

I understand that with async dependency such as HttpClient and EntityFramework the implementation for async methods exists already.

I am wondering whether it makes sense without such downstream async dependencies to pursue async webapi methods and the resulting a tad weird looking code.

3
  • Correction: public async Task Get(string name = _defaultName) Should have been: public async Task<IActionResult> Get(string name = _defaultName) Commented May 4, 2017 at 2:08
  • Task<IActionResult> DO NOT spawn another thread! Also async/await key words DO NOT spawn new threads. Only when Task.Run(...), TaskFactory.StartNew(...)` etc are called a new thread is spawned (this should be avoided in ASP.NET and ASP.NET Core) Commented May 4, 2017 at 11:20
  • This is opinion-based, but I think there's a place for all of these in the same project. Personally, I return models if possible (using exceptions to return error codes); but I also return IActionResult if necessary (e.g., file downloads); and I keep them synchronous unless they have asynchronous work to do, in which case Task<TModel> or Task<IActionResult> is appropriate. Trying to enforce a one-size-fits-all guidance will lead to antipatterns like Task.Run. Any decent dev should be able to figure out an appropriate return type, and be empowered change it if necessary. Commented May 4, 2017 at 15:00

1 Answer 1

4
  1. No control over http response, headers, code, etc - all set by the framework in case of success or failure, return type is visible to run-time tools

You shouldn't have to use dynamic ever, if you need to return arbitrary responses from an action, you can use

public IActionResult Get(string name = _defaultName)
{
    // or any other method which accepts a response object)
    return Ok(someModel);
}
  1. Better control over the http response, but it will tie up the thread while executing the method, no definition for the output data type

The request thread will be kept up for the duration of the action yes. But if you do not use any async operations (DB Access, a network connection to another website/webservice or read/write files to database), using it is fine.

// Here no async operations are called, so Task is not necessary
public IActionResult Index(string name = _defaultName)
{
    if(!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    return Ok();
}

For async operations you should use the Task<IActionResult> instead.

  1. Better control over http response, will not tie up the thread while executing the method, but will spin off another thread to compute the result, and no definition for the output type

This assumption is just wrong. Using Task alone with true async operations (I/O operations like reading from filesystem, network connections or access to database) will NOT SPAWN a new thread. The operation will be started and the thread returned to the thread pool.

No new thread will be started instead. When the async operation completes, a thread will be taken from the thread pool and operation continues.

// No extra thread is called and the request thread is only 
// used in between "await" calls
public async Task<IActionResult> Get(string name = _defaultName)
{
    var result = await GetResultFromDatabase();

    return Ok(someModel);
}

Please read the Async Programming : Introduction to Async/Await on ASP.NET article on MSDN from Stephen Cleary. It is about ASP.NET MVC 5 (the old webstack, not the rewrite ASP.NET Core) but the principle still applies to ASP.NET Core.

If you have CPU bound operation (calculating some complicated math etc.), then do not start a new thread. This will mess up with how ASP.NET Core manages the thread pool and you will gain nothing, but will still have the overhead of context-switches and hence it will lower your performance instead of increasing it.

For CPU bound operation, simply runt hem synchronously on the request thread.

public IActionResult Get(string name = _defaultName)
{
    // Don't await or run CPU intensive stuff via Task.Run
    var result = SomeCpuIntensiveOperation();

    return Ok(result);
}

You can also mix CPU bound and async operations

public async Task<IActionResult> Get(string name = _defaultName)
{
    // runs async, no extra thread
    var valueFromDb = await GetValueFromDb();

    // Don't await or run CPU intensive stuff via Task.Run
    // runs sync, on request thread
    var result = SomeCpuIntensiveOperation(valueFromDb);

    return Ok(result);
}

I am wondering whether it makes sense without such downstream async dependencies to pursue async webapi methods and the resulting a tad weird looking code.

That's for you to decide, based the new knowledge about your 3rd assumption

Generally, async scales better, when you have more requests than available threads in the thread pool. But most small sized applications never reach that state, especially when it's an learning exercise or a proof of concept.

It's very unlikely you hit that limit unless you have a high-traffic website which handles 100s or 1000s of requests per second.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for the feedback! "You shouldn't have to use dynamic ever" - it was not really a question about (not) using dynamic, unless you could share specific feedback on observed performance impact using dynamic in high-volume web/api apps. I am looking into poc for 10,000+ req/sec micro-services. Scalability is very important and ideally I am looking for a sound generic practice to recommend moving forward, e.g. use async Task<IActionResult> in use-cases a & b, and use IActionResult in use-cases d & e.
No idea about performance of dynamic, just test it. returning Dynamic or concrete type is same, in the end all of them will be wrapped around a IActionResult anyways. IActionResult is not only for http codes, but also for converting or processing the result stream. i.e. PhysicalFileResult will return a file as stream back to the client. As mentioned above, use IActionResult when you don't do async operations or cpu bound operations only.
Use Task<IActionResult> in all true async operations. Allocating/awaiting a task has a slight overhead (allocation of Task class and creation of a state machine when using await). In the end, you will have to do performance tests to decide it finally and it can't be answered here. The above explains how to use Task/async correctly in ASP.NET and ASP.NET Core. The most important correction of your assumption is that awaiting real Task do not spawn new thread and hence do not involve the "expensiveness" of starting a new thread!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.