3

I have a ASP .NET web api 2 application, and I'm trying to call asnyc method from sync method and meanwhile I encountered some issues. If I just call the method as a regular method the body after delay doesn't get executed, if I call it with Task.Run() it gets executed

public void CallingMethod(MethodExecutionArgs args)
{
  //do something with the args
            System.Diagnostics.Debug.WriteLine("BEFORE ");

            WriteFileToDiskAsync(args); // If I run only this, I never see "WriteFile() - AFTER delay" in the output
            Task.Run(async () => await WriteFileToDiskAsync(args)); // this executes the entire async method
            System.Diagnostics.Debug.WriteLine($"Finally written from sync method");

}

private async Task<bool> WriteFileToDiskAsync(dynamic file)
{
            System.Diagnostics.Debug.WriteLine("Before delay inside async");
            await Task.Delay(3000);
            System.Diagnostics.Debug.WriteLine("WriteFile() - AFTER delay");
}

Result: I always see the lines written from CallingMethod, but when I call the async method like a regular method, I only see the "Before delay inside async", If I call it with Task.Run() it see both lines from the async method.

Question1: Can someone explain this behavior, why doesn't the async method execute fully? Does it have something to do with what Stephen Cleary says: If you lose your AppDomain for any reason, that in-progress work is lost.

I read these articles but can't figure out why is this happening:

Context: What I'm trying to achieve is, on a existing API route that creates a X Resource (saves it in a database), after the X resource is created, I want to asynchronously call a method that will Create a json file with some information from that X Resource and save it in a filesystem. But even if the writing of the file fails, I want to return a successful response to the client (because his request was to actually save the X Resource - which succeeded) he doesn't care about Creating external file in XYZ filesystem.

Question2: What would you say is the best practice to achieve what I described above, considering all the existing methods chained in the CreateResource route are sync?

7
  • 2
    The best solution is: don't call async methods from non- async methods. What's stopping you from marking CallingMethod as async and having it return a Task? Commented Oct 30, 2020 at 15:49
  • Because the method that is calling the CallingMethod is sync and it is called by 60+ different methods which are called by N methods... And if I just make CallingMethod async and call it regularly from the sync method I would get the same behaviour as desribed above Commented Oct 30, 2020 at 15:58
  • 1
    I would definitely run that in a separate process, maybe send it to a queue first, so you can have full control of what happens with that file creation. Commented Oct 30, 2020 at 15:58
  • 1
    @guxxo So? Do a one time refactoring to make those async as well. You'll find that proper use of async means that async grows throughout the app. It's not too hard to to refactor the apps. Just make sure all your async calls are awaited, and that they're being called from methods that are marked as async and return a Task or Task<T>. It's a straightfoward refactor in most cases. Commented Oct 30, 2020 at 16:00
  • 2
    It's hard to imagine it taking a developer more than a single day to do this. It's a straight forward rinse-wash-repeat refactor. You do it once, then repeat the same process over and over. Commented Oct 30, 2020 at 16:40

1 Answer 1

1

Question1: Can someone explain this behavior, why doesn't the async method execute fully? Does it have something to do with what Stephen Cleary says: If you lose your AppDomain for any reason, that in-progress work is lost.

No. The AppDomain is still there in this case. In ASP.NET (pre-Core), there is an ASP.NET SynchronizationContext that represents the request. await by default captures the current context and uses that to resume executing its async method.

So, when the await completes, it attempts to resume executing on the SynchronizationContext for that request. However, that request has been completed, and so it doesn't make sense to resume on that context. The resulting behavior is undefined.

Question2: What would you say is the best practice to achieve what I described above, considering all the existing methods chained in the CreateResource route are sync?

If you are sure you want to return early, then you should have a background service handle creating the file, not the ASP.NET service. Since you have a database already, you could use the Outbox Pattern, where you place messages in an "outbox" table as part of the same transaction as your resource creation. Then a separate background service reads the "outbox" table and writes the JSON file.

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

Comments

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.