-1

I'm using c# .net core 5 Blazer WebService.

I have a service that has a list of Thing as a property.

public class Service : IService
{
    public HashSet<Thing> Things {get; set;}
}

Now I want to load the Things from disk, so I cache them privately in the service

public class Service : IService
{
    private HashSet<Thing> _things
    public HashSet<Thing> Things => _things : LoadThings();
}

But LoadThings() accesses the disk so I want that IO to run asynchronously and await it. But now LoadThings() needs to be async, and I can't do:

public class Service : IService
{
    private HashSet<Thing> _things
    public HashSet<Thing> Things => _things : await LoadThings();
}

And I get why; the async chain is lost.

So my question is this: What's the best way to get _things but await an async IO if _things is null?

Tod
  • 2,070
  • 21
  • 27
  • 1
    There are no asynchronous properties. Replace the property with a method that either returns the cached data or retrieves fresh data – Panagiotis Kanavos Apr 27 '21 at 17:26
  • 2
    If you want to lazily initialize the data instead of actually cache them with expiration you could check [AsyncLazy](https://github.com/StephenCleary/AsyncEx/wiki/AsyncLazy) – Panagiotis Kanavos Apr 27 '21 at 17:31
  • `But LoadThings() accesses the disk so I want that IO to run asynchronously` depending on what you're doing (in `LoadThings`), keep in mind this may not be the way to go and still may run synchronous; just a thought. – Trevor Apr 27 '21 at 17:39
  • Somewhat related: [Enforce an async method to be called once](https://stackoverflow.com/questions/28340177/enforce-an-async-method-to-be-called-once) – Theodor Zoulias Apr 27 '21 at 20:14

1 Answers1

1

When you say "cache" I'm assuming that you're referring to something that just loads and re-uses them vs "cache" with an expiration. That seems to match your existing code.

In that case you could do this:

    public class Service : IService
    {
        private Lazy<Task<HashSet<Thing>>> _things;

        public Service()
        {
            _things = new Lazy<Task<HashSet<Thing>>>(LoadThings);
        }

        public async Task MethodThatNeedsThings()
        {
            var things = await _things.Value;
            // Now you've got things and you can use them.
        }

        private async Task<HashSet<Thing>> LoadThings()
        {
            // This method asynchronously loads your things.
        }
    }

The things get asynchronously loaded the first time you need them, and after that they're available on subsequent calls.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • I was under the impression that doing complicated stuff like that in the constructor was generally a bad idea. This looks good to me, but what happens here is the IO throws an error? – Tod Apr 28 '21 at 07:40
  • 1
    @Tod Nothing complex is happening in the constructor. `LoadThings` is lazily evaluated when `_things.Value` is first accessed, so any exceptions would be thrown when `MethodThatNeedsThings` is invoked. – Johnathan Barclay Apr 28 '21 at 08:24