Aspnetcore: No way to run a blocking HTTP call from HttpClient

Created on 13 Apr 2018  路  13Comments  路  Source: dotnet/aspnetcore

All of the HTTP methods on HttpClient are declared as async methods that return Tasks of various flavors. There are no synchronous (blocking) methods.

The usual way to turn an async method into a blocking call is to call .Result on the Task. But calling HttpClient.GetJsonAsync<T>(myUrl).Result causes the entire app to hang.

The reason I need a blocking HTTP call is that I need to be able to load a configuration file from the server on app startup, and have it finished and processed before any pages begin to render. Under the current system, there does not appear to be any way to do this. Is there a way to do this? If not, can we get a fix for it?

area-blazor

Most helpful comment

You can run asynchronous operations before starting the renderer if you want to. For example,

static void Main(string[] args)
{
    Task.Run(async () =>
    {
        var serviceProvider = new BrowserServiceProvider(services =>
        {
            // Add any custom services here
        });

        // First run an async HTTP request
        var httpClient = serviceProvider.GetService<HttpClient>();
        var data = await httpClient.GetStringAsync("/api/SampleData/WeatherForecasts");
        Console.WriteLine("Fetched data: " + data);

        // Now the request is completed, start rendering the UI
        new BrowserRenderer(serviceProvider).AddComponent<App>("app");
    });
}

This is the preferred solution, rather than attempting to block the UI thread (which browsers don't allow - they'll say the tab has crashed if you're blocking the UI thread).

Though TBH this is still not as good a user experience as actually starting the renderer straight away and displaying something nice in the UI while waiting for whatever you need to load in the background.

All 13 comments

Normally you should await the async call:

var result = await HttpClient.GetJsonAsync<T>(myUrl);

Does that meet your needs?

@danroth27 No, because then the async call goes off into async-awaiting land, and Blazor renders my page before it returns. This is specifically the thing I need to make not happen, because proper rendering of my page depends on information in that config.

how about doing the call in Program.cs and delaying the call to

new BrowserRenderer(serviceProvider).AddComponent<App>("app");

@dlr1 Yeah, that's the idea that they've come up with on Gitter that looks like it might work. I'll have to test it out this evening when I get home.

You can run asynchronous operations before starting the renderer if you want to. For example,

static void Main(string[] args)
{
    Task.Run(async () =>
    {
        var serviceProvider = new BrowserServiceProvider(services =>
        {
            // Add any custom services here
        });

        // First run an async HTTP request
        var httpClient = serviceProvider.GetService<HttpClient>();
        var data = await httpClient.GetStringAsync("/api/SampleData/WeatherForecasts");
        Console.WriteLine("Fetched data: " + data);

        // Now the request is completed, start rendering the UI
        new BrowserRenderer(serviceProvider).AddComponent<App>("app");
    });
}

This is the preferred solution, rather than attempting to block the UI thread (which browsers don't allow - they'll say the tab has crashed if you're blocking the UI thread).

Though TBH this is still not as good a user experience as actually starting the renderer straight away and displaying something nice in the UI while waiting for whatever you need to load in the background.

@SteveSandersonMS Is this running in the browser UI thread? I thought there was a bunch of talk earlier about using Web Workers. Also, I'm not trying to block the browser UI thread; I'm trying to block the "thread" that the Mono interpreter is emulating until the browser is finished fetching the requested resource.

Having said that, the given solution looks like it will work. It just seems rather unfortunate that there's no way to say "I know what I'm doing, and in this particular case I need a synchronous HTTP call." I'm firmly in the "any baseline abstraction you can't get below when needed is evil" camp, because stuff like this does happen.

I think the browser freezing on any Task.Result or Task.Wait is still problematic. The issue is that if you have any code that waits on a task it is now impossible to use that code.

For example I have a library that exposes some data as properties. When a property is retrieved, it gets it's data from an http call. I don't mind rendering and then waiting for the call, but since properties cannot be async, I can't use my library anymore. It doesn't help to wrap the call in async and Threads do not work.

This would be true of properties made from Entity Framework. Although it might be far fetched to run EntityFramework directly in a browser there must be other cases.

It would be really nice if there was some way of wrapping the synchronous properties to tell the compiler to act like await would.

Properties that make async calls is a bad code smell to me. Sounds like you might want to refactor your code. I do however find in .NET the need to make async calls in event handlers which could be helpful in Blazor.

@TylerBrinkley Perhaps you are right about the code smell, but isn't that exactly how Entity Framework works. It lazy loads entities through properties. Aren't those async calls? Or are you saying there's no need on the client? Or perhaps that entity framework doesn't use any async calls.

It depends on how you use Entity Framework. It can be used synchronously or asynchronously. It is easier to use synchronously as you can easily just enumerate on a collection but for better throughput you really should use the async extension methods such as ToListAsync.

@TylerBrinkley Thanks for explaining that to me :) I didn't know about the async extensions for Entity Framework. I'll go ahead and modify my library so it has some asynchronous alternatives to the properties.

Hey, I've just encountered this issue myself - and I'm not sure what the preferred method to avoid it in my case would be:

I want a section of the app to require authentication - I've given it a separate layout and before rendering that layout I want to confirm that the user is authenticated.

My initial approach was just to use a blocking call to the server in OnInit, but obviously I can't do that.

EDIT: Errr, I think I came up with my solution immediately after submitting that... one moment while I make sure.

EDIT 2: Yep, I just wrapped the parts of the layout I don't want rendering in a conditional block...DUH

You can run asynchronous operations before starting the renderer if you want to. For example,

static void Main(string[] args)
{
    Task.Run(async () =>
    {
        var serviceProvider = new BrowserServiceProvider(services =>
        {
            // Add any custom services here
        });

        // First run an async HTTP request
        var httpClient = serviceProvider.GetService<HttpClient>();
        var data = await httpClient.GetStringAsync("/api/SampleData/WeatherForecasts");
        Console.WriteLine("Fetched data: " + data);

        // Now the request is completed, start rendering the UI
        new BrowserRenderer(serviceProvider).AddComponent<App>("app");
    });
}

This is the preferred solution, rather than attempting to block the UI thread (which browsers don't allow - they'll say the tab has crashed if you're blocking the UI thread).

Though TBH this is still not as good a user experience as actually starting the renderer straight away and displaying something nice in the UI while waiting for whatever you need to load in the background.

Any chance of getting a working example updated for the current (asp.net core 3 preview 5) version of Blazor? I have been unable to get any HttpClient to work in Main.

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

githubgitgit picture githubgitgit  路  3Comments

farhadibehnam picture farhadibehnam  路  3Comments

UweKeim picture UweKeim  路  3Comments

guardrex picture guardrex  路  3Comments

markrendle picture markrendle  路  3Comments