Home > database >  Blazor app slow loading due to multiple json downloading and parsing
Blazor app slow loading due to multiple json downloading and parsing

Time:07-14

I made a Blazor app that downloads and parses two json files. My app takes ages to load. Especially after adding the second json file download, things got slow (stuck at Loading... for a long time).

Even when I click any button with onclick event; one of them for example that shows a popup in a form of BlazoredModel; it so appears that the entire page starts loading again from scratch before loading the popup model. Even a simple onlick event with a static method takes forever to happen.

This is the code:

@if (players is null)
{
    <td>
        <p>Loading...</p>
    </td>
}
else
{
    @foreach(var player in players)
    {
        if (player.Steam == null)
            continue;

        @Task.Run( async() => await PlayerCivs(player.Steam, 'w')).Result //this line downloads/parses 1st json
        @Task.Run( async() => await GetMatchesHistory(player.Steam)).Result //this line downloads/parses 2st json
        etc..

@code {
    private static System.Timers.Timer aTimer;
 
    private async Task showPOP()
    {
        ControlID.IDplayer = 34;
        modal.Show<Edit>("Edit Player");
    }

    public async Task<decimal> PlayerCivs(string steamid, char f)
    {
        var dnl = await DownloadStringAsync(steamid, @"https://aoe2.net/api/player/ratinghistory?game=aoe2de&leaderboard_id=3&steam_id=",1);

        var playerRank = PlayerRank.FromJson(dnl);

        numWINS = playerRank[0].NumWins;
        numLOSS = playerRank[0].NumLosses;
        numStreak = (int)playerRank[0].Streak;
        numRate = (int)playerRank[0].Rating;
        Perc = Math.Round(((decimal)playerRank[0].NumWins / ((decimal)playerRank[0].NumLosses   (decimal)playerRank[0].NumWins)) * 100, MidpointRounding.AwayFromZero).ToString();
        return Math.Round(((decimal)playerRank[0].NumWins / ((decimal)playerRank[0].NumLosses   (decimal)playerRank[0].NumWins)) * 100, MidpointRounding.AwayFromZero);
    }

    public async Task<int> GetMatchesHistory(string steamid)
    {
        string dnl = "";
        Thread thread = new Thread(() => {dnl = DownloadStringAsync(steamid, @"https://aoe2.net/api/player/matches?game=aoe2de&steam_id=", 500).Result; });
        thread.Start();

        while (dnl == "")
        {
            await Task.Delay(10);
        }

        //var dnl = await DownloadStringAsync(steamid, @"https://aoe2.net/api/player/matches?game=aoe2de&steam_id=",500);
        var matchesHistory = MatchesHistory.FromJson(dnl);
        //3 best civs
        //most played map

       List<decimal> mapnums = new List<decimal>();
       List<decimal> allmapnums = new List<decimal>();

       if (matchesHistory == null)
            return 0;

       foreach (var item in matchesHistory)
       {
            foreach (var pl in item.Players)
            {
                foreach(var j in item.Players)
                {
                    if (pl.SteamId == steamid)
                        allmapnums.Add((decimal)j.Civ);

                    if(pl.SteamId == steamid && pl.Won == true)
                    {
                        mapnums.Add((decimal)j.Civ);
                    }
                }
            }
        }

        var first = mapnums.GroupBy(i => i)
                           .OrderByDescending(grp => grp.Count())
                           .Select(grp => grp.Key).ElementAt(0);
        var second = mapnums.GroupBy(i => i)
                            .OrderByDescending(grp => grp.Count())
                            .Select(grp => grp.Key).ElementAt(1);
        var third = mapnums.GroupBy(i => i)
                           .OrderByDescending(grp => grp.Count())
                           .Select(grp => grp.Key).ElementAt(2);

        firstNUM = first;
        secondNUM = second;
        thirdNUM = third;

        //times repeated first => decimal

        percfirst = Math.Round((Repeatedx(first, mapnums) / Repeatedx(first, allmapnums)) * 100, MidpointRounding.AwayFromZero);

        percsecond = Math.Round((Repeatedx(second, mapnums) / Repeatedx(second, allmapnums)) * 100, MidpointRounding.AwayFromZero);

        percthird = Math.Round((Repeatedx(third, mapnums) / Repeatedx(third, allmapnums)) * 100, MidpointRounding.AwayFromZero);

        return 0;
    }

    async Task<string> DownloadStringAsync(string id,string iurl,int cnt, int timeOut = 60000)
    {
        using (HttpClient client = new HttpClient())
        {
        //client.DefaultRequestHeaders.Add("User-Agent", "C# console program");
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        //var content = client.GetStringAsync(new Uri(iurl   id   @"&count=" cnt)).Result;
        
            return await client.GetStringAsync(new Uri(iurl   id   @"&count="   cnt));
        }
    }

    protected override async Task OnInitializedAsync()
    {
        StartTimer();
        
        numROWS = A();
        players = await _db.GetPlayers();
    }

    public void StartTimer()
    {
        aTimer = new System.Timers.Timer(2000);
        aTimer.Elapsed  = CountDownTimer;
        aTimer.Enabled = true;
    }

    public async void CountDownTimer(Object source, ElapsedEventArgs e)
    {
        if (A() != numROWS)
        {
            players = await _db.GetPlayers();
            numROWS = A();
        }
        else
            return;

        InvokeAsync(StateHasChanged);
    }
    etc.
}

I'm using 11 Nuget packages:

  • Blazored.Modal.
  • CurrieTechnologies.Razor.Clipboard.
  • Microsoft.EntityFrameworkCore.
  • Microsoft.EntityFrameworkCore.Design.
  • Microsoft.EntityFrameworkCore.Tools.
  • MySqlConnector.
  • Newtonsoft.Json.
  • Pomelo.EntityFrameworkCore.MySql.
  • System.Data.Entity.Repository.
  • System.Runtime.Serialization.Json.
  • System.Text.Json.

Why is it so slow rendering? Am I missing something or doing something wrong?

I could add more code from program.cs or appsettings.json or anything you request. I'm stuck with this for 1 months now and it's my first Blazor project.

Thank you very much

CodePudding user response:

As you have found out, Rendering (the razor markup/html code) can be executed quite often.

So the normal practice is to load data in OnInitializedAsync(). Or OnParamersSetAsync() when your data relies on parameters. See this picture.

Now your core code is

await Task.Run(async () => await PlayerCivs(player.Steam, 'w'));
await Task.Run(async () => await GetMatchesHistory(player.Steam));
await Task.Run(async () => await fw());

We can improve on that. It uses 3 async lambda methods that don't do anything but do cost a little. Task.Run() will accept any Task, we can make it:

await Task.Run(() => PlayerCivs(player.Steam, 'w'));
await Task.Run(() => GetMatchesHistory(player.Steam));
await Task.Run(() => fw());

The 3 methods now run async (with respect to the Blazor framework) but still sequential. I would expect the following to run faster (time = Max(a,b,c) instead of time = a b c)

var t1 = Task.Run(() => PlayerCivs(player.Steam, 'w'));
var t2 = Task.Run(() => GetMatchesHistory(player.Steam));
var t3 = Task.Run(() => fw());
await Task.WhenAll(t1,t2,t3);

Furthermore, Task.Run() is ineffective in Blazor Wasm and doubtful on Blazor Server. Do try:

var t1 = () => PlayerCivs(player.Steam, 'w');
var t2 = () => GetMatchesHistory(player.Steam);
var t3 = () => await fw();
await Task.WhenAll(t1,t2,t3);

CodePudding user response:

Thanks to @HenkHolterman I learnt an important lesson in Blazor. "Even if you can do it, it does not mean It works as perfect as you want" - so even if Blazor lets us implement Tasks and methods directly into the html, It's counter-performance especially Tasks that download strings or data from the internet (Apis).

I did move my foreach loop with the Tasks that download json strings to OnitializedAsync() and that ladies and gents fixed some bugs where when I click any button the page reloads from scratch for no reason and reduced the rendering time of the browser.

Coming to the second issue which is also related to slow rendering is the fact that I was not calling StateHasChanged(); whenever I make changes to my model object. It looks like this

protected override async Task OnInitializedAsync()
    {
        StartTimer();
        numROWS = A();
        
        //Grabbing mysql data and parsing it to my model object which is "players"
        players = await Task.Run(async () => await _db.GetPlayers());
        //Calling Statehaschanged to asynchronously re-render my model object into html without reloading the entire page.
        StateHasChanged();
        //Here I'm reading the object model and parsing more data into it.
        foreach (var player in players)
        {
            if (player.Steam == null)
                continue;

            await Task.Run(async () => await PlayerCivs(player.Steam, 'w'));
            await Task.Run(async () => await GetMatchesHistory(player.Steam));
            await Task.Run(async () => await fw());
            player.WinRate = PLAYERATE;
            player.Civ1 = await GetCivName(firstNUM);
            player.Civ1alt = civALT;

            player.Civ2 = await GetCivName(secondNUM);
            player.Civ2alt = civALT;

            player.Civ3 = await GetCivName(thirdNUM);
            player.Civ3alt = civALT;
            //Again!! We call statehaschanged to update the table row concerned with the model object item.
            StateHasChanged();
        }


    }

By calling StateHasChanged() a few time, I'm updating my html table row by row and even component by component if I want to.

Although this is the answer to my question which I struggled to fix for a month, I'm still obliged to credit @HenkHolterman and mark his reply as the answer because without his tip to move all tasks to the code section is the solution to this problem and helped me figure other things too. So thank you again Henk <3

  • Related