2014-09-22 13 views
5

Ho questo codice per aggiungere oggetto e campo indice in Stackexchange.Redis. Tutti i metodi nel thread di blocco delle transazioni. Perché?I metodi di transazione StackExchange.Redis si bloccano

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

Per cosa intendo non ancora disponibile: vedere "in coda" qui: http://redis.io/topics/transactions –

risposta

11

comandi eseguiti all'interno di una transazione non ritornano risultati fino a dopo si esegue l'operazione. Questa è semplicemente una funzione di come funzionano le transazioni in Redis. Al momento si è in attesa di qualcosa che non è stato ancora inviato (le transazioni vengono memorizzate localmente fino a quando non vengono eseguite), ma anche se è stato inviato: i risultati non sono disponibili fino al completamento della transazione.

Se si desidera che il risultato, è necessario memorizzare (non attendere) il compito, ed attendere che dopo l'esecuzione:

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

Si noti che questo è più conveniente di quanto sembri: quando la transazione viene eseguita, le attività nidificate ottengono i loro risultati allo stesso tempo - e lo await gestisce lo scenario in modo efficiente.

+2

Strana logica, ma funziona! Grazie! – boostivan

+0

@boostivan ci vuole del tempo per abituarsi; nota - un altro modo per farlo è usare 'Script *' e inviare uno script Lua che faccia le operazioni sul server. Molto più semplice –

+0

@MarcGravell Qual è la procedura migliore se faccio _non_ sono necessari i risultati del/dei comando/i? Catturare i compiti e "aspettarli" dopo la transazione comunque, o sparare e dimenticare? (Indovinando il primo, ma voglio solo essere sicuro.) –

0

La risposta di Marc funziona, ma nel mio caso ha causato una notevole quantità di codice gonfio (ed è facile dimenticarsi di farlo in questo modo), così mi è venuta in mente un'astrazione che impone lo schema.

Ecco come lo si utilizza:

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

Ecco l'implementazione:

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

Un avvertimento: questo non fornisce un modo semplice per arrivare al risultato di uno dei comandi. Nel mio caso (e negli OP) va bene - utilizzo sempre le transazioni per una serie di scritture. Ho trovato che questo mi ha davvero aiutato a tagliare il mio codice, e solo esponendo lo (che richiede di restituire un'attività), io sono meno propenso a "dimenticare" che non dovrei essere await in quei comandi al momento Li chiamo.

Problemi correlati