-1

I have a problem where I have a library that uses an async function, GetParametersByPathAsync (which is defined here: https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Services/SimpleSystemsManagement/Generated/_mobile/AmazonSimpleSystemsManagementClient.cs#L2718)

I have a library function defined as

Task<Dictionary<string,string>> GetAllParameters(string region)
{
    var pars = DoParameterGatheringWork(reigion);
    ...(do some more work)
    return dict;
}

which calls another method

async Task<Dictionary<string,string>> DoParameterGatheringWork(string region)
{
    ...
    var response = await GetParametersByPathAsync(requestObj);
    ... (process the response)
    return parameterDict;
}

that awaits on the GetParametersByPathAsync and gathers things.

This is a problem because GetAllParameters has to be called by my service from a static constructor and initialize a parameter Dictionary<string,string> MyParameters { get; }

I would like to stop this bubbling up of Tasks at some point in the library, so it can just expose Dictionary<string,string> GetAllParameters(string region), not the Task version. (I am completely fine with it becoming synchronous..)

I don't think I should be just doing Task.Wait() or Task.Result either because that will cause deadlocks.

Maybe this isn't the best way to go about it, but I am unsure how to continue on from here.

Any thoughts would be appreciated!

Edit:

Here is the constructor code I would like to have:

public class MyConfiguration
{
    static MyConfiguration()
    {
        ...
        Parameters = ServiceConfiguration.GetAllParameters(); // (library call)
    }
    public static Dictionary<string, string> Parameters { get; }
}

and the client will be able to use this anywhere just by MyConfiguration.Parameters["IAMAPARAMETER"]

smbl
  • 314
  • 3
  • 13
  • 2
    Sounds like you're looking for [Async Initialization](https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html) – JSteward Jan 05 '18 at 15:40
  • So you just want to call an async function synchronously? – DavidG Jan 05 '18 at 15:42
  • @DavidG yes. But without running into a deadlock problem. – smbl Jan 05 '18 at 15:44
  • @JSteward this looks good. I will look into this thank you – smbl Jan 05 '18 at 15:44
  • https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c – DavidG Jan 05 '18 at 15:45
  • @DavidG possibility of deadlock with those solutions isn't there? – smbl Jan 05 '18 at 15:46
  • No there isn't, did you read them? – DavidG Jan 05 '18 at 15:46
  • @DavidG yes I did. I do like the solution of AsyncContext.Run(MyAsyncMethod); but then again, he goes on the talk about AsyncContext.RunTask won't work in every scenario. – smbl Jan 05 '18 at 15:54
  • 1
    @smbl *why* do you want to stop bubbling? Sync means block. *Blocking* wastes CPU because it typically starts with spinwaiting before putting the thread to sleep. Especially in cloud scenarios you *don't* blocking or burning CPU doing nothing. Nowadays even console applications have an `async Task Main()`. – Panagiotis Kanavos Jan 05 '18 at 16:21
  • @PanagiotisKanavos I should probably give some context. So AWS used to have a synchronous version of the function and once I upgraded to DotnetCore, they enforced async methods and removed all sync methods. Previous behavior was fine as it returned `Dictionary` all the time, but now it returns `Task` breaking all client's codes. Breaking is fine,, but I have yet to find an alternative method to initialize the dictionary, thus haven't found the right justification for it yet. (In other words, my async knowledge isn't extensive enough to go down this route myself) – smbl Jan 05 '18 at 16:24
  • @smbl to put it another way, I *hate* libraries that block on async calls to expose a fake synchronous API. It means that I have to modify the code to remove the block so it won't waste *my* CPU and harm my application's performance. – Panagiotis Kanavos Jan 05 '18 at 16:24
  • @DavidG That question contains numerous answers that will deadlock, and lots of answers with different types of problems, even if those problems aren't deadlocks. The solution to the problem that will actually reliably work consistently is to simply *not mix synchronous and asynchronous code*; your solution should be either entirely synchronous or entirely not, or you're going to have problems. – Servy Jan 05 '18 at 16:24
  • @smbl good for them, I approve wholehearteadly. It's *Core* which means *light*, scalable and asynchronous. Its users *expect* proper asynchronous behaviour. If I waste 20% CPU due to blocking, it means I need 5 VMs or Lambdas instead of 4 for the same load. In a high-traffic environment 20% can be a conservative estimate – Panagiotis Kanavos Jan 05 '18 at 16:28
  • @PanagiotisKanavos I have added more context in the Edit above. hopefully what I am trying to achieve is more visible – smbl Jan 05 '18 at 16:29
  • @smbl yes, and you are chasing the wrong problem. You want to use a configuration source that loads data from Amazon. ASP.NET Core's configuration architecture has no problem with that, eg check any of the examples that use EF Core as the source. Users of your library would expect it to work with the Configuration infrastructure instead of bypass it. – Panagiotis Kanavos Jan 05 '18 at 16:33
  • @smbl to explain my loathing, check [this library](https://github.com/checkout/checkout-net-library). I needed it to retrieve several hundred ticket records at a time. The author used HttpClient and then "helpfully" blocked the calls to give a "sync" interface. Hundreds of blocks at a time aren't fun. Then check [the settings class](https://github.com/checkout/checkout-net-library/blob/master/Checkout.ApiClient.Net45/Helpers/AppSettings.cs) which expects certain hard-coded entries. I wanted to request data for multiple credentials. I ended up rewriting it – Panagiotis Kanavos Jan 05 '18 at 16:36
  • @smbl now check the [EF Core config provider sample](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?tabs=basicconfiguration#create-an-entity-framework-custom-provider). You only need to inherit from ConfigurationProvider and implement Load. It doesn't hurt to block there. If it does, you can use caching or Lazy so that you only block when someone asks for the first entry and then only if it hasn't been loaded yet – Panagiotis Kanavos Jan 05 '18 at 16:41
  • @smbl perhaps you should check this SO question [How do I handle async operations in Startup.Configure?](https://stackoverflow.com/questions/32257640/how-do-i-handle-async-operations-in-startup-configure) – Panagiotis Kanavos Jan 05 '18 at 16:42
  • @PanagiotisKanavos thanks for your input. the devil in me wants me to go down the `task.GetAwaiter().GetResult()` (which I just verified it works ...) But my head is telling me take this opportunity to make this configuration library sexier and learn some async programming. Would you be open to guiding me through if I do want to go this route? ( I have looked at the `ConfigurationProvider` and I am unsure if that's the way I want to go down) – smbl Jan 05 '18 at 16:48
  • @smbl The only difference between `GetAwaiter().GetResult()` and `GetResult` is that any exceptions aren't wrapped in an aggregate exception in the former. Other than that it's identical. It's only an aesthetic difference to calling `Result`. – Servy Jan 05 '18 at 16:58

1 Answers1

0

After comment: at the end of this answer: How to call the async method from a non-async method

Apparently DoParameterGatheringWork is a function that normally would have to do some busy waiting for another process, like a database, or a file, or some information from the internet.

The designer of that function thought it would be a waste of time if your thread would be waiting idly for the result of this remove action. Therefore he decided to make it async, so the callers could do other things while the other process would process the request.

You saw correct that this means that all callers should also be async, and that a constructor can't be async.

If you want to benefit from the advantages of async-await (meaning that your callers can continue processing instead of idly waiting, make your constructor ligthweight and let some Create function do the async job you normally would do in the constructor. Force everyone who wants an object of your class to use this async Create function.

public class MyConfiguration
{
    // Static async Create function, does everything you'd normally do in the constructor:
    public static async Task<MyConfiguration> CreateAsync()
    {
        Dictionary<string,string> allParameters = await ServiceConfiguration.GetAllParameters(...);
        MyConfiguration createdConfiguration = new MyConfiguration(allParameters);
        return createdConfiguration;
     }

     // make sure that no one except CreateAsync can call the constructor:
     private MyConfiguration(Dictionary<string,string> allParameters)
     {
          parameters = allParameters;
     }
}

What you do is that you make the constructor as lightweight as possible and do all the difficult stuff, including await in the CreateAsync function.

Usage:

The following will lead to compiler error, so you know the problem before you start running:

MyConfiguration config = new MyConfiguration(...);

Proper usage:

async Task<...> DoSomethingAsync(...)
{
     ...
     // I need a configuration:
     MyConfiguration config = await MyConfiguration.Create();
     // from here you can use the fully initialized MyConfiguration object:
     config.DoSomethingElse();
     ...
}

Simple comme bonjour

Addition: how to call async method from a non-async function

To call an async function from a non-async method, use Task.Run to start the async function, Task.Wait to wait for the async function to complete and Task.Result to get the return value of the async function.

static void Main(string[] args)
{
    // call an async function:
    var asyncTask = Task.Run( () => MyAsyncFunction(...));

    // if desired: do other things
    DoSomethingElse();

    // now you need the result of the async function
    asyncTask.Wait();
    var returnValue = asyncTask.Result;
    Process(returnvalue);
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Almost. This would pass the private constructor, but this configuration class will be needed in the very beginning of the app in Startup.cs ConfigureServices method. And this method cannot be async. As far as I know, This is the lowest I can go in the call chain (or highest, whichever you look at it).. – smbl Jan 09 '18 at 22:33