StackExchange.Redis transaction methods freeze

I have this code to add an object and index field to Stackexchange.Redis. All methods in the transaction freeze the thread. Why?

var transaction = Database.CreateTransaction(); //this line freeze thread. WHY ? await transaction.StringSetAsync(KeyProvider.GetForID(obj.ID), PreSaveObject(obj)); await transaction.HashSetAsync(emailKey, new[] { new HashEntry(obj.Email, Convert.ToString(obj.ID)) }); return await transaction.ExecuteAsync(); 
+8
source share
3 answers

Commands executed within a transaction do not return results until after they complete the transaction. This is just a feature of transaction work in Redis. At the moment, you expect that it has not yet been sent (transactions are buffered locally until execution), but even if they were sent: the results are simply not available until the transaction is completed.

If you want to get the result, you must save (not wait) the task and wait for it after execution:

 var fooTask = tran.SomeCommandAsync(...); if(await tran.ExecuteAsync()) { var foo = await fooTask; } 

Note that this is cheaper than it looks: when a transaction is executed, nested tasks get their results at the same time - and await handles this script efficiently.

+15
source

Marc's answer works, but in my case it caused a decent amount of code bloat (and it's easy to forget to do it that way), so I came up with an abstraction that imposes a pattern.

Here's how you use it:

 await db.TransactAsync(commands => commands .Enqueue(tran => tran.SomeCommandAsync(...)) .Enqueue(tran => tran.SomeCommandAsync(...)) .Enqueue(tran => tran.SomeCommandAsync(...))); 

Here's the implementation:

 public static class RedisExtensions { public static async Task TransactAsync(this IDatabase db, Action<RedisCommandQueue> addCommands) { var tran = db.CreateTransaction(); var q = new RedisCommandQueue(tran); addCommands(q); if (await tran.ExecuteAsync()) await q.CompleteAsync(); } } public class RedisCommandQueue { private readonly ITransaction _tran; private readonly IList<Task> _tasks = new List<Task>(); public RedisCommandQueue Enqueue(Func<ITransaction, Task> cmd) { _tasks.Add(cmd(_tran)); return this; } internal RedisCommandQueue(ITransaction tran) => _tran = tran; internal Task CompleteAsync() => Task.WhenAll(_tasks); } 

One caveat: this does not provide an easy way to get the result of any of the commands. In my case (and OP) this is normal - I always use transactions for a series of records. I found that it really helped truncate my code, and only exposing the tran inside Enqueue (which requires you to return the Task), I am less likely to forget that I should not await these commands at the time of input call them.

+1
source

My team and I were bitten by this problem several times, so I created a simple Roslyn analyzer to identify such problems.

https://github.com/olsh/stack-exchange-redis-analyzer

0
source

Source: https://habr.com/ru/post/1203143/


All Articles