2014-09-22 11 views
5

Mam ten kod, aby dodać obiekt i pole indeksu w Stackexchange.Redis. Wszystkie metody w wątku zatrzymania transakcji. Dlaczego?Metody transakcji StackExchange.Redis zawieszają się

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

Co mam na myśli przez jeszcze nie dostępne: patrz "w kolejce" tutaj: http://redis.io/topics/transactions –

Odpowiedz

11

Polecenia wykonywane wewnątrz transakcji nie zwracają wyników aż po wykonać transakcję. Jest to po prostu cecha działania transakcji w Redis. W tej chwili oczekujesz na coś, co jeszcze nie zostało wysłane (transakcje są buforowane lokalnie do momentu wykonania) - ale nawet jeśli zostały wysłane: wyniki po prostu nie są dostępne do zakończenia transakcji.

Jeśli chcesz wynik należy zapisać (nie czekają) zadania i czekają go po execute:

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

Należy pamiętać, że jest to tańsze niż to wygląda: kiedy transakcja wykonywana, zadania zagnieżdżone otrzymują wyniki w tym samym czasie - i await wydajnie obsługuje ten scenariusz.

+2

Dziwna logika, ale działa! Dziękuję Ci! – boostivan

+0

@boostivan przyzwyczajenie się do myślenia; uwaga - innym sposobem na to jest użycie 'Script *' i wysłanie skryptu Lua, który wykonuje operacje na serwerze. Znacznie prostsze. –

+0

@MarcGravell Jaka jest najlepsza praktyka, jeśli robię _nie_ potrzeba wyników komend (y)? Uchwyć zadania i "czekaj" na nich po transakcji, czy też strzel i zapomnij? (Zgaduję, kto pierwszy, ale chcę tylko mieć pewność.) –

0

Odpowiedź Marka działa, ale w moim przypadku spowodowała przyzwoitą ilość kodu (i łatwo jest zapomnieć zrobić to w ten sposób), więc wymyśliłem abstrakcję, która wymusza ten schemat.

Oto jak go używać:

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

oto realizacja:

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

Jedno zastrzeżenie: nie zapewniają łatwy sposób uzyskać na wyniku którejkolwiek z polecenia. W moim przypadku (i OP) wszystko jest w porządku - zawsze używam transakcji dla serii zapisów. Zauważyłem, że to naprawdę pomogło w przycięciu kodu w dół i tylko wystawiając tran wewnątrz Enqueue (co wymaga, abyś zwrócił zadanie), mniej "zapomnę", że nie powinienem być await tymi poleceniami w tym czasie Wzywam ich.

Powiązane problemy