Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I handle async operations in Startup.Configure?

In my ASP.NET 5 app, I want to load some data from Azure into a cache inside my Startup.Configure method. The Azure SDK exposes async methods exclusively. Typically, calling an async method is done via await inside an async method, like this:

public async Task Configure(IApplicationBuilder app, IMemoryCache cache) {     Data dataToCache = await DataSource.LoadDataAsync();     cache.Set("somekey", dataToCache);      // remainder of Configure method omitted for clarity } 

However, ASP.NET 5 requires that the Configure method returns void. I could use an async void method, but my understanding is that async void methods are only supposed to be used for event handlers (as per https://msdn.microsoft.com/en-us/magazine/jj991977.aspx among many others).

I was thinking that a better way to do this would be to call the async function without await, call Wait on the returned Task, then cache the results via the Task.Results property, like this:

public void Configure(IApplicationBuilder app, IMemoryCache cache) {     Task<Data> loadDataTask = DataSource.LoadDataAsync();     loadDataTask.Wait();     cache.Set("somekey", loadDataTask.Result);      // remainder of Configure method omitted for clarity } 

Stephen Walther used a similar approach in a blog post earlier this year. However, it's unclear from that post if this is considered an acceptable practice. Is it?

If this is considered an acceptable practice, what - if any - error handling do I need? My understanding is that Task.Wait() will re-throw any exceptions thrown by the async operation and I haven't provided any mechanism to cancel the async operation. Is simply calling Task.Wait() sufficient?

like image 510
DevHawk Avatar asked Aug 27 '15 19:08

DevHawk


People also ask

What happens when you call async method?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

How do you call async method in MVC action?

STEP 01 Create new MVC Application project, named as "Async". In the File menu, click New Project. In the "New Project" dialog box, under Project types, expand Visual C#, and then click "Web". In the Name box, type "Async", then click on Ok.

Should I use async in EF core?

Async operations are important for keeping a responsive UI in rich client applications, and can also increase throughput in web applications where they free up the thread to service other requests in web applications.


2 Answers

The example code in the blog you linked to was only using sync-over-async to populate a database with example data; that call wouldn't exist in a production app.

First, I'd say that if you truly need Configure to be asynchronous, then you should raise an issue with the ASP.NET team so it's on their radar. It would not be too difficult for them to add support for a ConfigureAsync at this point (that is, before release).

Second, you've got a couple of approaches to the problem. You could use task.Wait (or better yet, task.GetAwaiter().GetResult(), which avoids the AggregateException wrapper if an error does occur). Or, you could cache the task rather than the result of the task (which works if IMemoryCache is more of a dictionary than some weird serialize-into-binary-array-in-memory thing - I'm looking at you, previous versions of ASP.NET).

If this is considered an acceptable practice, what - if any - error handling do I need?

Using GetAwaiter().GetResult() would cause the exception (if any) to propagate out of Configure. I'm not sure how ASP.NET would respond would be if configuring the application failed, though.

I haven't provided any mechanism to cancel the async operation.

I'm not sure how you can "cancel" the setup of an application, so I wouldn't worry about that part of it.

like image 171
Stephen Cleary Avatar answered Sep 23 '22 13:09

Stephen Cleary


Dotnet Core 3.x offers better support for this.

First, you could create a class for your caching process. Have it implement IHostedService like below. There are just two functions to implement:

    private readonly IServiceProvider _serviceProvider;     public SetupCacheService(IServiceProvider serviceProvider)     {         _serviceProvider = serviceProvider;     }      public async Task StartAsync(CancellationToken cancellationToken)     {         // Perform your caching logic here.          // In the below example I omit the caching details for clarity and          // instead show how to get a service using the service provider scope.          using (var scope = _serviceProvider.CreateScope())         {             // Example of getting a service you registered in the startup             var sampleService = scope.ServiceProvider.GetRequiredService<IYourService>();              // Perform the caching or database or whatever async work you need to do.              var results = sampleService.DoStuff();             var cacheEntryOptions = new MemoryCacheEntryOptions(){ // cache options };              // finish caching setup..         }     }      public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; 

Now, in Starup.cs

    public virtual void ConfigureServices(IServiceCollection services)     {         // Normal service registration stuff.          // this is just an example. There are 1000x ways to do this.          services.AddTransient(IYourService, ConcreteService);         // Here you register the async work from above, which will         // then be executed before the app starts running        services.AddHostedService<SetupCacheService>();     } 

And that's it. Note that my solution to this relied heavily on Andrew Lock's article. I'm very grateful for him taking the time to write that up.

From the link I posted by Andrew Lock,

The services will be executed at startup in the same order they are added to the DI container, i.e. services added later in ConfigureServices will be executed later on startup.

Hopefully this helps anyone looking for Dotnet core 3.x+ approach.

like image 25
joshmcode Avatar answered Sep 19 '22 13:09

joshmcode