DEV Community

Cover image for Building a Reactive TUI: Implementing Real-Time Refresh Pattern with C# and Redis
Raziq Din
Raziq Din

Posted on

Building a Reactive TUI: Implementing Real-Time Refresh Pattern with C# and Redis

In a standard Terminal User Interface (TUI), the screen is usually static.
 
To see new messages, a user would typically have to manually refresh or the app would have to "poll" the database every second , which is expensive and laggy.
 
By using Redis Publish and Subscription Method, we can implement a "Signal" pattern: the database handles the permanent storage, while Redis acts as the instant notification system that tells the TUI exactly when to re-render the messages once users send messages to one another in a chat room.

 

1. Installation and Setup

To follow this implementation, you need the Redis server running and the official .NET client.

 

Step 1: Run Redis via Docker

Instead of a complex local installation, use Docker to spin up a lightweight instance in your command prompt:

docker run --name tui-redis -p 6379:6379 -d redis

Enter fullscreen mode Exit fullscreen mode

 

Step 2: Add the NuGet Package

In your C# project directory, install the StackExchange.Redis library via NuGet Package Manager.

dotnet add package StackExchange.Redis

Enter fullscreen mode Exit fullscreen mode

 

2. The "Signal" Implementation

The core logic is split into two parts: the Sender (who publishes the signal) and the Receiver (who listens for the signal).

For my project architecture are as follows :


|
|-- Services/
|   |-- RedisMessagingServices.cs  (The Publisher)
|
|-- View/
|   |-- ChatwithContactView.cs     (The Subscriber & Renderer)

Enter fullscreen mode Exit fullscreen mode

 

The Sender: Publishing the Refresh Event

In your messaging service, after you successfully insert a message into your SQL database, you immediately notify the receiver’s channel.

C#
// Inside RedisMessagingServices.cs
public bool insertMessage(string sender, string receiver, string content) {
    if (db.Save(sender, receiver, content)) {
        var sub = redis.GetSubscriber();
        // Signal the specific receiver to refresh their view
        sub.Publish($"messages:{receiver}", "REFRESH_CHAT");
        return true;
    }
}

Enter fullscreen mode Exit fullscreen mode

 

The Receiver: Listening in the Background

In the TUI View, we subscribe to our own channel. This happens in the background, so the user can keep typing while the app waits for a signal.

C#
// Inside ChatwithContactView.cs
var sub = connectionMultiplexer.GetSubscriber();

sub.Subscribe($"messages:{currentUser}", (channel, message) => {
    if (message.ToString() == "REFRESH_CHAT") {
        _needRefresh = true; // Set the flag to trigger a re-render
    }
});
Enter fullscreen mode Exit fullscreen mode

 

3. The Reactive Render Loop

To make the TUI feel "live" without flickering, we use a while(true) loop that only updates the screen when the _needRefresh flag is true.

C#
while (true) {
    if (_needRefresh) {
        RenderFullChat(currentUser, contact, inputBuffer.ToString());
        _needRefresh = false; // Reset the flag after rendering
    }

    // Check for non-blocking keyboard input
    if (Console.KeyAvailable) {
        // Handle typing logic...
    }

    Thread.Sleep(50); // Keep CPU usage low
}
Enter fullscreen mode Exit fullscreen mode

 

Redis Architecture:

This optimization could be done with the help of Redis' Publish/Subscribe Architecture! When an event occurs , like a user sending a message or a team scoring a goal , your server publishes a message to that specific channel. Redis immediately pushes this message to every active subscriber, allowing the application to trigger a "refresh" or update the UI in real-time.

 

Why This Works

This approach combines the best of both worlds:

  • Data Integrity: Your SQL database remains the "Source of Truth" for long-term message history.
  • Instant Experience: Redis Pub/Sub provides sub-millisecond signaling, ensuring that the moment a message is sent, the receiver's terminal clears and re-renders with the new data.
  • Resource Efficiency: By only re-rendering on a REFRESH_CHAT signal, you avoid unnecessary CPU cycles and screen flickering, resulting in a professional-grade CLI experience.

 

Final Implementation Note:

Always remember to call sub.Unsubscribe(channelName) when the user exits the chat view. This ensures your application doesn't leave "ghost" subscriptions open in the background, keeping your Redis memory usage clean and optimized.

Top comments (0)