Search code examples
c#while-loopblazor-server-side

In Blazor server side app, I have endless while loop for consuming data from Kafka topic in my razor.cs page. How can I prevent it blocking the UI?


In my razor.cs file I have following async Task that is consuming data from Kafka topic in an endless while loop. When consuming is started the User Interface is blocked and the user cannot leave page or do other actions. How can I prevent this? I want to keep the while loop. Normaly I would like to close the connection with Dispose, when user leaves the page

my razor.cs code:

protected override void OnInitialized()
{
  Read_Status_from_Kafka();
}


public async Task Read_Status_from_Kafka()
{
  // code for configuring Kafka connection
  while (true)
  {
     //consuming data form Kafka topic
  }
}

Solution

  • As you have discovered, a thread [in this case the synchronisation context] can only do one thing at a time. You're expectation was that you can put it in a monitor loop and respond to UI events at the same time.

    If you want to read the data all the time then a background task is a good solution.

    If you only want to read the data when the page is active you can use a timer to do this. This "offloads" the operation of your while loop to the timer which is already running such a loop on a dedicated thread to handle all the registered timers.

    Here's a demo page that sets up a timer on a page to run every second and update the UI if something changes.

    @page "/"
    @implements IDisposable
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <div class="bg-dark text-white p-2 m-2">
        <pre>@_message</pre>
    </div>
    
    @code{
        private Timer? _timer;
        private string? _message;
        private volatile bool _isProcessing;  //mark as volatile for thread safely
    
        protected override void OnInitialized()
        {
            // Kick off the timer on the Timer Service
            _timer = new Timer(this.OnTimerExpired, null, 1000, 1000);
        }
    
        // Note that this method will be executed on a threadpool thread by the Timer service
        // not on the UI thread [synchronisation context]
        // isProcesssing ensures we only run one OnTimerExpired at once
        private async void OnTimerExpired(object? state)
        {
            if (_isProcessing)
                return;
    
            _isProcessing = true;
            //fake Read_Status_from_Kafka();
            await Task.Delay(500);
    
            // Update the UI if something has changed
            if(true)
            {
                _message = $"Updated at {DateTime.Now.ToLongTimeString()}";
                // invoke the UI update on the UI thread [synchronisation context]
                await this.InvokeAsync(StateHasChanged);
            }
            _isProcessing = false;
        }
    
        public void Dispose()
        {
            _timer?.Dispose();
        }
    }