vrcks
vrcks

Reputation: 477

IReliableDictionary TryGetValueAsync() throws exception "The key already existed in the dictionary"

I am trying to update a data item stored in reliable dictionary as below:

private async Task UpdateDictionary(IEnumerable<long> keys)
{
    var dataDictionary = await this.stateManager.GetOrAddAsync<IReliableDictionary2<long, DataItem>>("DataItemsDictionary");
    using (var tx = this.stateManager.CreateTransaction())
    {
        // When I replace parallel with foreach, it works fine.
        keys.AsParallel().ForAll(async k =>
        {
            // Below line is line 54 in the exception
            var dataItem = await dataDictionary.TryGetValueAsync(tx, k);
            if (dataItem.HasValue)
            {
                ServiceEventSource.Current.Message(dataItem.Value.ToString());

                var newItem = new DataItem(dataItem.Value);
                newItem.TransmissionState = TransmissionState.InReview;
                await dataDictionary.SetAsync(tx, newItem.Id, newItem);
            }
        });

        await tx.CommitAsync();
    }
}

System.ArgumentException occurred
  HResult=0x80070057
  Message=The key already existed in the dictionary.
  Source=mscorlib
  StackTrace:
   at System.Collections.Concurrent.ConcurrentDictionary`2.System.Collections.Generic.IDictionary<TKey,TValue>.Add(TKey key, TValue value)
   at System.Fabric.Store.TStore`5.CreateStoreReadWriteTransaction(Transaction replicatorTransaction)
   at System.Fabric.Store.TStore`5.CreateOrFindTransaction(Transaction replicatorTransaction)
   at Microsoft.ServiceFabric.Data.Collections.DistributedDictionary`2.<TryGetValueAsync>d__117.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at StateService.StateManager.DataItemTask.<>c__DisplayClass2_1.<<UpdateDictionary>b__0>d.MoveNext() in D:\SFApplication\StateManager\Tasks\DataItemTask.cs:line 54

I am not understanding why TryGetValueAsync funtion is trying to add to the dictionary. It should ideally just get the element.

Strangely, when I remove the parallel query and replace it with foreach, it works fine. Is it not safe to use parallel within a transaction?

Upvotes: 0

Views: 724

Answers (1)

Wouter B
Wouter B

Reputation: 731

Transactions cannot handle parallel requests to the same transaction. This is described on the documentation page for IReliableDictionary2:

The transaction is the unit of concurrency. Users can have multiple transactions in-flight at any given point of time, but for a given transaction each API must be called one at a time. When calling any asynchronous Reliable Collection method that takes an Microsoft.ServiceFabric.Data.ITransaction, you must wait for completion of the returned Task before calling another method using the same transaction.

Parallel doesn't work and foreach does work because you must wait for completion of the returned Task before calling another method using the same transaction.

Upvotes: 1

Related Questions