Home > Back-end >  What's the generally accepted best practice when calling NON-Async functions, from within other
What's the generally accepted best practice when calling NON-Async functions, from within other

Time:09-17

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.

  1. 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.

  2. 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.

  1. 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 a Task.Run, if this is beneficial for them.

  2. You can wrap the ValidateEvent in a Task.Run, like you did in your question. This violates (partially) another guideline by Microsoft: Do not expose asynchronous wrappers for synchronous methods.

  3. You can split your AddAsync method into two parts, the synchronous ValidateAdd and the asynchronous AddAsync, 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.

  • Related