0

---UPDATE---

I can now say that EVERY time the Timer fails to trigger, I get the following error message in Debug Output. And when I get the error, the Timer fails 100% of the time.

Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR [https://localhost:44371/_framework/blazor.server.js] Exception thrown: 'Microsoft.AspNetCore.Connections.ConnectionResetException' in Microsoft.AspNetCore.Server.IIS.dll

However, the live site (on a Windows server) also fails to trigger the Timer sometimes, so I don't think it's just VS debugging issue.

---/UPDATE---

I have an animated svg on my foyer page which lasts 3 seconds. I would like to display additional controls after 3 seconds: Login button, "Take a tour" and so on, so I've added a Timer which is started in OnAfterRender (also tried OnAfterRenderAsync).

The problem is that about 1/3 of the time (randomly as rarely as 1/20 or more), the handler for the timer doesn't trigger, and the controls are not shown. I'm assuming it's some kind of threading race condition, but I don't really understand what can be done about it.

I've tried:

  • using async lifecycle events and handler
  • await ()=> expressions and so on in various combinations

(Note-- I know I can used animated CSS to do it, but I'm limit testing the use of Timers right now)

Here's some simplified code:

<MyCoolAnimation / >
@if (ShowControls)
{
    // various controls
}

@code {
    bool ShowControls;
    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
        {
            Timer = new Timer(3000);
            Timer.Elapsed += CountDownTimer;
            Timer.Start();
        }    
    }
    private async void CountDownTimer(Object source, System.Timers.ElapsedEventArgs e)
    {
        Timer.Stop();
        Timer.Dispose();
        ShowControls = true;

        await InvokeAsync(StateHasChanged);
    }
}
Bennyboy1973
  • 3,413
  • 2
  • 11
  • 16
  • Please read [ask] and then [mcve]. Then please provide us with the complete code for the various things you've tried and instructions for us to replicate your issue. Right now your question doesn't seem to contain enough information for us to give you a good answer. A good question gets answers within minutes here. – Enigmativity Jul 17 '21 at 02:34
  • The instruction is put any content you want in the @if block, and hit reload about 20 times, maybe 30. I cannot give a more minimal reproducible example than this. Nor can I list the many ways in which I've tried to use `async Tasks` or lifecycle overrides to find anything different, as I've tried very many with no discernable change in the outcome. – Bennyboy1973 Jul 17 '21 at 02:59
  • "I cannot give a more minimal reproducible example than this." - Sure you can. If I just create a Blazor app and paste in your code it doesn't even run for me. Please create a sample app that demonstrates your issue and give us the instructions to get that app running. That shouldn't be hard to do. – Enigmativity Jul 17 '21 at 04:28
  • I use a heartbeat like event from a service. 1 timer for the whole app. The delegate passes either a count or the current datetime. Either way you can then use this to calculate the duration. – Brian Parker Jul 17 '21 at 04:40
  • Blazor Server or WebAssemmbly? How/Where is Timer declared? You are close to a [mre] but just not there. – H H Jul 17 '21 at 05:40
  • @Brian that's quite a clever idea, I like it. – Bennyboy1973 Jul 17 '21 at 05:49
  • @Henk. This is Blazor Server. This is the foyer page-- it uses an empty layout page and has nothing but the animation and a couple of buttons to redirect-- either to a login page or to a tour. It has no dependencies or injections, other than `NavigationManager`. – Bennyboy1973 Jul 17 '21 at 05:54
  • You will have to provide a full [mre] now. This 'simplified' code doesn't cut it. – H H Jul 17 '21 at 06:24
  • @HenkHolterman why would it fire if you unsubscribe? – Brian Parker Jul 17 '21 at 07:14
  • It wouldn't but you didn't mention unsubscribe. So you still need all the IDisposable boilerplate. – H H Jul 17 '21 at 08:40

3 Answers3

1

if (!firstRender) is strange - you will have many timers running.

And that kinda provides an answer, the Timer = new Timer(3000); and the Timer.Dispose(); parts have a race condition (on Blazor Server at least).

  • make sure you only have 1 Timer instance on your page
  • create and start it in OnInitialized()
  • use @implements IDisposable to clean up

Here is some sample code.

H H
  • 263,252
  • 30
  • 330
  • 514
  • Oops-- I actually accepted this as the answer, but after changing to `if(firstRender)`, it failed again. It's very rare-- like maybe 1/20 page loads or so. – Bennyboy1973 Jul 17 '21 at 06:47
  • The problem is not where you think it is then. Create a new scratch project and try to reproduce it. – H H Jul 17 '21 at 06:59
  • I have some additional info now. I'll add it to the OP. – Bennyboy1973 Jul 17 '21 at 07:12
  • After googling, I see this error seems to be a recent problem with a Windows update. I'm worried that other events in the site might be broken randomly as well. – Bennyboy1973 Jul 17 '21 at 07:20
  • Update-- moving the initializing to OnItialized, only creating new Timer if it's null, etc., didn't have any effect. If necessary, I can create an answer with the current state of the page. – Bennyboy1973 Jul 17 '21 at 07:59
  • And do you have a Dispose pattern already? Because it probably doesn't fail on the first time the page is shown. – H H Jul 17 '21 at 08:52
  • I'm currently using exactly as Enet posted, with a couple apparent typos cleaned up. I stripped out everything else except a bit of html markup. I'm not sure what you mean by "first time the page is shown." Do you mean (firstRender), or just when the page is first loaded? In the latter case, it does in fact fail randomly-- so users go to my site, and play roulette on whether the login components will display. – Bennyboy1973 Jul 17 '21 at 13:28
1

In this case I would avoid the entire Timer issue.

You can do this witout a Timer, Dispose pattern or InvokeAsync. Just keeping it simple:

<MyCoolAnimation / >
@if (ShowControls)
{
    // various controls
}

@code {
  bool ShowControls;

  protected override async Task OnInitializedAsync()
  {
    ShowControls  = false;
    await Task.Delay(3_000);
    ShowControls  = true;
  }
}

I have my doubts about calling Timer.Dispose() inside the elapsed handler. Couldn't find a authorative statement about it though.

H H
  • 263,252
  • 30
  • 330
  • 514
  • Yeah, I wasn't not sure about that either, but I've tried it without `Dispose` or `Stop` and it doesn't change anything. I can't figure out how to get a stack trace, because I haven't been able to catch the `net::ERR_HTTP2_PROTOCOL_ERROR` error, or the accompanying `'Microsoft.AspNetCore.Connections.ConnectionResetException' in Microsoft.AspNetCore.Server.IIS.dll` that comes with it in `catch try` blocks. One thing, though-- in watching debug output, that error comes right away on page load, rather than after 3 seconds. So I see the error, say "Okay, it won't pop," and then it doesn't. – Bennyboy1973 Jul 17 '21 at 22:43
  • 1
    Just quit it, Bennyboy1973. ConnectionResetException is all about IIS, and it has nothing to do with the timer – enet Jul 17 '21 at 23:41
  • @enet Yeah, I'm pretty over it at this point. ty for trying to help though, I appreciate it. – Bennyboy1973 Jul 19 '21 at 10:27
0

(v3)

Last update:

I'm pretty over this. Thanks for helping, guys! Right now, the following suggestion from Henk works. I did a Windows update, and I thought that was the issue-- but I still can't get the Timer version to work properly. I'm cautiously optimistic that the issue is with the lifecycle, not with Timers in general.

protected override async Task  OnAfterRenderAsync(bool firstRender)
{
    if (!ShowControls)
    {
        await Task.Delay(3000);
        ShowControls = true;
        StateHasChanged();
    }
}

It seems to me that instantiating a Timer object inside a Blazor lifecyle even might just be too finicky to get it to work reliably.

My only hope is that other things I use Timer for (like popping up queued messages through a service) won't ALSO break 5% of the time.

Bennyboy1973
  • 3,413
  • 2
  • 11
  • 16
  • Why do you put it in OnAfterRender ? That is not the 'normal spot' unless you have some JS going on. – H H Jul 18 '21 at 06:09
  • No particular reason. I was just experimenting and limit testing, and that's the event I was trying out when I figured that the effect was no longer failing. I'm planning to make a player-vs-player quiz game using Timers in a service, and I want to get to know the territory better. – Bennyboy1973 Jul 18 '21 at 10:38