2014-09-22 9 views
5

Ich habe diesen Code, um Objekt und Indexfeld in Stackexchange.Redis hinzuzufügen. Alle Methoden in Transaktion einfrieren Thread. Warum?StackExchange.Redis Transaktionsmethoden frieren ein

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(); 
+0

Für was ich meine noch nicht verfügbar: siehe "in Warteschlange" hier: http://redis.io/topics/transactions –

Antwort

11

Befehle innerhalb einer Transaktion ausgeführt nicht zurück Ergebnisse bis nach Sie die Transaktion aus. Dies ist nur ein Merkmal dafür, wie Transaktionen in Redis funktionieren. Im Moment warten Sie auf etwas, das noch nicht einmal gesendet wurde (Transaktionen werden lokal gepuffert, bis sie ausgeführt werden) - aber selbst wenn es gesendet wurde: sind einfach nicht verfügbar bis die Transaktion abgeschlossen ist.

Wenn Sie das Ergebnis wollen, sollten Sie speichern die Aufgabe (nicht erwarten), und erwarten es nach das ausführen:

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

Beachten Sie, dass diese billiger ist, als es aussieht: wenn die Transaktion ausführt, Die verschachtelten Aufgaben erhalten ihre Ergebnisse zur gleichen Zeit - und await behandelt dieses Szenario effizient.

+2

Seltsame Logik, aber es funktioniert! Vielen Dank! – boostivan

+0

@boostivan braucht es etwas Denken, um sich zu gewöhnen; note - Eine andere Möglichkeit, dies zu tun, ist 'Script *' zu verwenden und ein Lua-Skript zu senden, das die Ops alle auf dem Server ausführt. Viel einfacher. –

+0

@MarcGravell Was ist die beste Vorgehensweise, wenn ich das/die Ergebnis (se) der Befehle nicht benötige? Erfassen Sie die Aufgaben und "warten" Sie nach der Transaktion trotzdem, oder feuern und vergessen? (Erraten, aber ich will nur sicher sein.) –

0

Marcs Antwort funktioniert, aber in meinem Fall verursachte es eine anständige Menge an Code Bloat (und es ist leicht zu vergessen, es auf diese Weise zu tun), also kam ich mit einer Abstraktion, die das Muster erzwingt.

Hier ist, wie Sie es verwenden:

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

Hier ist die Umsetzung:

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); 
} 

Eine Einschränkung: Dies ist nicht eine einfache Möglichkeit bieten am Ergebnis irgendeines der bekommen Befehle. In meinem Fall (und den OPs) ist das in Ordnung - ich verwende immer Transaktionen für eine Reihe von Schreibvorgängen. Ich fand, dass dies wirklich geholfen hat, meinen Code zu reduzieren, und indem ich nur tran innerhalb Enqueue exponiert (was erfordert, dass Sie eine Aufgabe zurückgeben), bin ich weniger wahrscheinlich zu "vergessen", dass ich nicht diese Befehle zu der Zeit sein sollte Ich rufe Sie an.

Verwandte Themen