What is the best way to call a async method in a sync method?

Vote:
 

Hi there,

I did a bit of research on how to call an async method in a sync method in Epi, but I could not find the "final" recommendation. So let's say inside of the method ProcessPayment present in the IPaymentPlugin I want to call an async method, what is the best way? I found the async helper below but I'm not sure if Episer recommends it. Let me know your thoughts, thank you!

public static class AsyncHelper  
{
    private static readonly TaskFactory _taskFactory = new
        TaskFactory(CancellationToken.None,
                    TaskCreationOptions.None,
                    TaskContinuationOptions.None,
                    TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        => _taskFactory
            .StartNew(func)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    public static void RunSync(Func<Task> func)
        => _taskFactory
            .StartNew(func)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

Episerver version:

CMS:11.20.0

Commerce:13.25.0

#229929
Edited, Oct 27, 2020 1:05
Vote:
 

I use the same helper methods all the time, when I need to call async code from a sync method (for instance payment plugins). I have never had any issues with it.

So I would definitely say, go ahead.

#229933
Oct 27, 2020 6:52
Vote:
 

This is not Episerver-related question - just saying. I don't think you need to use TaskFactory at all. Just Task.Run is enough 

#229937
Oct 27, 2020 8:38
Vote:
 

I'll get the popcorns now :D

@Valdis, where are you? you need to join to this topic.

I suppose it would be good to reference the sources of "I copied this helper from somewhere", most likely we can link to this SO-thread because the code in the initial post looks like copy+paste of MS internal asynchelper.

I know I have used in some code: xxxx.GetAwaiter().GetResult();

Also have spent some time on looking for the real solution of the safe way "calling from sync async code".

@Quan yes not an Episerver issue as is BUT Episerver has in some places only async implementation which you must call from sync and that becomes kind of Episerver issue because there are no "sync" overloads, it would be nice to have "Episerver" recommendation how to call - at least when it is wrong, it is easy to fix :D

I've also looked at https://docs.microsoft.com/en-us/archive/blogs/jpsanders/asp-net-do-not-use-task-result-in-main-context for reference and I do know I have somewhere countless amount of links to different postings about this - but the problem is always that one needs to understand the context.

Read also Stephen Cleary old but valid MSDN Magazine article: https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development

(and his blog has tons of info https://blog.stephencleary.com/2012/02/async-and-await.html there is a starting point)

And also his answer in SO: https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c/9343733#9343733

@Valdis, your turn to paste links here ;-)

#230255
Nov 02, 2020 18:19
Vote:
 

Hi, 

Using the AsyncHelper describe on your code, will not help you on long running tasks if need, for example. Task.Run is quite simitar with TaskFactory.StartNew, that automatic unwrap the results and have default values, but does not allow you to control how to behave and choose the scheduler, like if you really want that thread to be used is from the thread pool.

As Antti, presented, lot's of blogs and posts about this. I think for each case, it must be choosed quite carefull which to use.

#230256
Edited, Nov 02, 2020 22:05
Vote:
 

I've also used this helper and clearly I'm not the only one! Here's a whole blog post by Māris which reaches the same conclusion (albeit in the context of events): https://marisks.net/2017/04/02/calling-async-methods-within-episerver-events/

@Antti: I actually remember Valdis recommending this same helper on a forum question a while back (but related to Scheduled Jobs): https://world.episerver.com/forum/developer-forum/-Episerver-75-CMS/Thread-Container/2019/5/safe-way-to-call-async-method-in-episerver-scheduled-job/

Of course, there are a lof of caveats here and it really depends on your implementation and the async method you're calling.

#230257
Edited, Nov 02, 2020 22:09
Vote:
 

And here is a good explanation of the Task.Run vs Task.Factory.StartNew: https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/

So when reading that, I would say why use the helper method from the first post when you could just use: Task.Run(...)

  • instead of creating a new taskfactory like the helper does you would use the existing one
  • Task.Run targets the TaskScheduler.Default same as the helper
  • the difference with the helper and Task.Run are in these options:
    • TaskCreationOptions.None (Specifies that the default behavior should be used.) VS TaskCreationOptions.DenyChildAttach
    • TaskContinuationOptions.None (When no continuation options are specified, specifies that default behavior should be used when executing a continuation.) VS ???

And if you read post from Stephen about the same subject: https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

The last line says:  Just use Task.Run(() => A());

So we have the same mess now in this forum thread about calling async from sync what we have is SO and all over the place :D

#230283
Nov 03, 2020 5:25
Vote:
 

Mess is everywhere around async, even after many years of introduction of the subject to the .NET world. Why? Because there is not silver bullet and it depends.

Async is not easy and one should really understand the context and consequences of each of the technique and mechanics behind.

 

@Jake – AsyncHelper is the easiest way to call async method in context when your async code is “slim” enough and does not require much (request contexts, etc.) Therefore I replied “give it a try” :)

 

My 2 humble cents here would be - @Quan, yes it is Episerver related topic. Why? Because Episerver is constraining developer and setting is in “sync box”. Of course this is easy to say, but if you look at many modern / latest versions of the libraries, helpers, frameworks, etc. – many of them are providing only “async” path. For example the same ASP.NET Core. So IMHO if I have to use library that provides just async methods in a “runtime” (Episerver) that executes only in sync context (in specific components - like scheduled jobs and other extension points) – it is a problem of a “underlying” framework and not the developer who might be forced to call async methods.

 

#230361
Edited, Nov 04, 2020 20:01
Vote:
 
#230447
Nov 05, 2020 18:31
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.