Transitioning to Unwatch

Welcome back! Previously, you learned about using the watch command to implement controlled transactions in Redis. This powerful feature allows you to monitor keys and ensure updates are made safely when specific conditions are met. Now, we'll build upon that knowledge and introduce the unwatch command, which will give you even more control over your transactions by allowing you to cancel the effects of a watch.

What You'll Learn

In this lesson, you'll dive into enhancing transaction control using the unwatch command. Specifically, you will learn:

  1. Using unwatch to Cancel Monitored Keys: How to stop monitoring keys when certain conditions within your transaction are not met.
  2. Implementing Conditional Updates with unwatch: Writing functions that ensure changes are only made when valid and safe to do so.

Let's walk through a practical example to make this concept clearer:

using System;
using StackExchange.Redis;

class RedisExample
{
    private static ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
    private static IDatabase db = redis.GetDatabase();

    public static void UpdateBalance(string userId, int increment)
    {
        var balanceKey = $"balance:{userId}";
        while (true)
        {
            var tran = db.CreateTransaction();
            tran.AddCondition(Condition.KeyExists(balanceKey));
            
            var balance = (int)(db.StringGet(balanceKey) ?? 0);

            if (balance + increment < 0)
            {
                tran.Execute(CommandFlags.FireAndForget);  // Using FireAndForget to mimic unwatch
                break;
            }
            
            tran.StringSetAsync(balanceKey, balance + increment);

            bool committed = tran.Execute();
            if (committed)
            {
                break;
            }
        }
    }

    static void Main(string[] args)
    {
        db.StringSet("balance:1", 100);
        UpdateBalance("1", 50);
        Console.WriteLine($"Updated balance for user 1: {db.StringGet("balance:1")}");
        
        UpdateBalance("1", -200);  // This will not succeed due to the negative balance check
        UpdateBalance("1", -50);  // This will succeed
        Console.WriteLine($"Final balance for user 1: {db.StringGet("balance:1")}");
    }
}

In this example, the tran.Execute(CommandFlags.FireAndForget); within the UpdateBalance method ensures that if our condition to prevent a negative balance is not met, the transaction monitoring is effectively canceled.

Let's understand why we use this approach in this context:

  • Preventing Negative Balances: We want to ensure that the user's balance does not go below zero. If the increment would result in a negative balance, we cancel the transaction. Just breaking out of the loop without managing the transaction state could lead to incorrect behavior in subsequent transactions, especially in a multi-threaded environment.
  • Code Readability: Ensuring explicit handling of conditions makes the code more readable. It clearly communicates that the transaction is being canceled due to a specific condition not being met. Though simpler cases might work with just looping logic, ensuring transaction state consistency is crucial.
Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal