It seems that in a Blazor Server app we are encouraged to use Async where possible, and I generally understand why. That said - would someone please be kind enough to explain the general expectation when using Async functions and fill in some missing knowledge for me - I'm confused by all the information that's out there and need something specific to what I'm doing, to help me understand.
I'm trying to stick to using async where possible and in most cases it's fairly easy to do so, but when calling some functions, it seems overkill to make them asynchronous (or is it?). In the the example below - the NON-async 'ValidateEvent' function is being called from within an async function.
So my question is ...Should I:
a) Call it normally from within the Async function (which seems to defeat the point of async) eg: "var validationResult = ValidateEvent(objDto);"?
b) Call it using Task.Run eg: "await Task.Run(() =>ValidateEvent(objDto));"?
c) Convert this simple IF/ELSE method into an Async function?
Thanks in advance for any help/advice.
//example async function, that itself calls the other non-async function.
public async Task<Response> AddAsync(ObjDto objDto)
{
// a) Call it normally?
var validationResult = ValidateEvent(objDto);
// b) Calling it using Task.Run?
var validationResult = await Task.Run(() =>ValidateEvent(objDto));
//Do stuff asynchronously here
...
await _db.AddAsync(objDto);
await _db.SaveChangesAsync();
...
}
The validation function:
c) Should I really be converting this to async since it's just a series of 'IFs and ELSEs' (async conversion further below)
//Non-Async version
public ResponseObj ValidateEvent(ObjDto obj)
{
ResponseObj responseObj = new();
string stringErrors = "";
//If not full day, check that end date is not before start date
if (!obj.IsFullDay)
{
if (obj.EndTime < obj.StartTime)
stringErrors = ("End date cannot be before the start date<br />");
}
...other code removed for brevity
if (string.IsNullOrEmpty(stringErrors)) //Success
{
responseObj.Status = responseObj.Success;
}
else //errors
{
responseObj.Status = responseObj.Error;
responseObj.Message = stringErrors;
}
return responseObj;
}
//Example Async conversion - is it worth converting this using Task.Run?
//Async conversion
public async Task<ResponseObj> ValidateEvent(ObjDto obj)
{
ResponseObj responseObj = new();
string stringErrors = "";
await Task.Run(() =>
{
//If not full day, check that end date is not before start date
if (!obj.IsFullDay)
{
if (obj.EndTime < obj.StartTime)
stringErrors = ("End date cannot be before the start date<br />");
}
if (string.IsNullOrEmpty(stringErrors)) //Success
{
responseObj.Status = responseObj.Success;
}
else //errors
{
responseObj.Status = responseObj.Error;
responseObj.Message = stringErrors;
}
}
);
return responseObj;
}
Again, thanks in advance for any help/advice in understaning the best way to go with this in general.
CodePudding user response:
a) Call it normally from within the Async function ...
Yes
... (which seems to defeat the point of async)
No it doesn't.
CodePudding user response:
It depends on what the ValidateEvent
method does.
In case it does a trivial amount of work that is not going to be increased over time, like your example suggests, then it's completely OK to invoke it from inside the asynchronous
AddAsync
method.In case it blocks for a considerable amount of time, say for more than 50 msec, then invoking it from inside an asynchronous method would violates Microsoft's guidelines about how asynchronous methods are expected to behave:
An asynchronous method that is based on TAP can do a small amount of work synchronously, such as validating arguments and initiating the asynchronous operation, before it returns the resulting task. Synchronous work should be kept to the minimum so the asynchronous method can return quickly.
So what you should do if the ValidateEvent
method blocks, and you can't do anything about it? AFAIK you are in a gray area, with all options being unsatisfactory for one reason or another.
You can leave it as is, accepting that the method violates Microsoft's guidelines, document this behavior, and leave to the callers the option to wrap the
AddAsync
in aTask.Run
, if this is beneficial for them.You can wrap the
ValidateEvent
in aTask.Run
, like you did in your question. This violates (partially) another guideline by Microsoft: Do not expose asynchronous wrappers for synchronous methods.You can split your
AddAsync
method into two parts, the synchronousValidateAdd
and the asynchronousAddAsync
, expecting that the callers will invoke the one method after the other.
If your AddAsync
method is part of an application-agnostic library, I would say go with the option 1. That's what Microsoft is doing with some built-in APIs. If the AddAsync
method is application-specific, and the application has a GUI (WinForms, WPF etc), then it is tempting to go with the option 2. Keeping the UI responsive is paramount for a GUI application, so the Task.Run
should be included somewhere, and putting it inside the AddAsync
seems like a convenient place to put it. The option 3 is probably the least attractive option for this particular case, because validating and adding are tightly coupled operations, but it may be a good option for other scenarios.