Como: Adicionar e remover itens de um ConcurrentDictionary
Este exemplo mostra como adicionar, recuperar, atualizar e remover itens de um System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>. Essa classe de coleção é uma implementação de thread-safe. Recomendamos que você usá-lo sempre que vários threads talvez esteja tentando acessar os elementos simultaneamente.
ConcurrentDictionary<TKey, TValue>fornece vários métodos de conveniência que tornam desnecessário o código para primeiro verificar se uma chave existe antes de tentar adicionar ou remover dados. A tabela a seguir lista esses métodos de conveniência e descreve quando usá-los.
Método |
Use quando … |
---|---|
Você deseja adicionar um novo valor para uma chave especificada e, se a chave já existir, você deseja substituir seu valor. |
|
Você deseja recuperar o valor existente para uma chave especificada e, se a chave não existir, você deseja especificar um par chave/valor. |
|
Você deseja adicionar, obter, atualizar ou remover um par chave/valor e, se a chave já existir ou se a tentativa falhar por qualquer outro motivo, você deseja executar alguma ação alternativa. |
Exemplo
O exemplo a seguir usa duas Task instâncias para adicionar alguns elementos para um ConcurrentDictionary<TKey, TValue> simultaneamente e então apresenta todo o conteúdo para mostrar que os elementos foram adicionados com êxito. O exemplo também mostra como usar o AddOrUpdate, TryGetValue, GetOrAdd, e TryRemove métodos para adicionar, atualizar, recuperar e remover itens da coleção.
Imports System
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks
Namespace DictionaryHowToVB
' The type of the value to store in the dictionary
Class CityInfo
Private _name As String
Property Name As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _lastQueryDate As DateTime
Property LastQueryDate As DateTime
Get
Return _lastQueryDate
End Get
Set(ByVal value As DateTime)
_lastQueryDate = value
End Set
End Property
Private _longitude As Decimal
Property Longitude As Decimal
Get
Return _longitude
End Get
Set(ByVal value As Decimal)
_longitude = value
End Set
End Property
Private _latitude As Decimal
Property Latitude As Decimal
Get
Return _latitude
End Get
Set(ByVal value As Decimal)
_latitude = value
End Set
End Property
Private _highTemps() As Integer
Property RecentHighTemperatures As Integer()
Get
Return _highTemps
End Get
Set(ByVal value As Integer())
_highTemps = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal key As String)
_name = key
' MaxValue means "not initialized"
_longitude = Decimal.MaxValue
_latitude = Decimal.MaxValue
_lastQueryDate = DateTime.Now
_highTemps = {0}
End Sub
Public Sub New(ByVal name As String, ByVal longitude As Decimal,
ByVal latitude As Decimal, ByVal temps As Integer())
_name = name
_longitude = longitude
_latitude = latitude
_highTemps = temps
End Sub
End Class
Class Program
' Create a new concurrent dictionary with the specified concurrency level and capacity.
Shared cities As New ConcurrentDictionary(Of String, CityInfo)(System.Environment.ProcessorCount, 10)
Shared Sub Main()
Dim data As CityInfo() =
{New CityInfo With {.Name = "Boston", .Latitude = 42.358769, .Longitude = -71.057806, .RecentHighTemperatures = {56, 51, 52, 58, 65, 56, 53}},
New CityInfo With {.Name = "Miami", .Latitude = 25.780833, .Longitude = -80.195556, .RecentHighTemperatures = {86, 87, 88, 87, 85, 85, 86}},
New CityInfo With {.Name = "Los Angeles", .Latitude = 34.05, .Longitude = -118.25, .RecentHighTemperatures = {67, 68, 69, 73, 79, 78, 78}},
New CityInfo With {.Name = "Seattle", .Latitude = 47.609722, .Longitude = -122.333056, .RecentHighTemperatures = {49, 50, 53, 47, 52, 52, 51}},
New CityInfo With {.Name = "Toronto", .Latitude = 43.716589, .Longitude = -79.340686, .RecentHighTemperatures = {53, 57, 51, 52, 56, 55, 50}},
New CityInfo With {.Name = "Mexico City", .Latitude = 19.432736, .Longitude = -99.133253, .RecentHighTemperatures = {72, 68, 73, 77, 76, 74, 73}},
New CityInfo With {.Name = "Rio de Janiero", .Latitude = -22.908333, .Longitude = -43.196389, .RecentHighTemperatures = {72, 68, 73, 82, 84, 78, 84}},
New CityInfo With {.Name = "Quito", .Latitude = -0.25, .Longitude = -78.583333, .RecentHighTemperatures = {71, 69, 70, 66, 65, 64, 61}}}
' Add some key/value pairs from multiple threads.
Dim tasks(1) As Task
tasks(0) = Task.Factory.StartNew(Sub()
For i As Integer = 0 To 1
If cities.TryAdd(data(i).Name, data(i)) Then
Console.WriteLine("Added {0} on thread {1}", data(i).Name, Thread.CurrentThread.ManagedThreadId)
Else
Console.WriteLine("Could not add {0}", data(i))
End If
Next
End Sub)
tasks(1) = Task.Factory.StartNew(Sub()
For i As Integer = 2 To data.Length - 1
If cities.TryAdd(data(i).Name, data(i)) Then
Console.WriteLine("Added {0} on thread {1}", data(i).Name, Thread.CurrentThread.ManagedThreadId)
Else
Console.WriteLine("Could not add {0}", data(i))
End If
Next
End Sub)
' Output results so far
Task.WaitAll(tasks)
' Enumerate data on main thread. Note that
' ConcurrentDictionary is the one collection class
' that does not support thread-safe enumeration.
For Each city In cities
Console.WriteLine("{0} has been added", city.Key)
Next
AddOrUpdateWithoutRetrieving()
RetrieveValueOrAdd()
RetrieveAndUpdateOrAdd()
Console.WriteLine("Press any key")
Console.ReadKey()
End Sub
' This method shows how to add key-value pairs to the dictionary
' in scenarios where the key might already exist.
Private Shared Sub AddOrUpdateWithoutRetrieving()
' Sometime later. We receive new data from some source.
Dim ci = New CityInfo With {.Name = "Toronto", .Latitude = 43.716589, .Longitude = -79.340686, .RecentHighTemperatures = {54, 59, 67, 82, 87, 55, -14}}
' Try to add data. If it doesn't exist, the object ci is added. If it does
' already exist, update existingVal according to the custom logic in the
' delegate.
cities.AddOrUpdate(ci.Name, ci, Function(key, existingVal)
' If this delegate is invoked, then the key already exists.
' Here we make sure the city really is the same city we already have.
' (Support for multiple keys of the same name is left as an exercise for the reader.)
If (ci.Name = existingVal.Name And ci.Longitude = existingVal.Longitude) = False Then
Throw New ArgumentException("Duplicate city names are not allowed: {0}.", ci.Name)
End If
' The only updatable fields are the temerature array and lastQueryDate.
existingVal.LastQueryDate = DateTime.Now
existingVal.RecentHighTemperatures = ci.RecentHighTemperatures
Return existingVal
End Function)
' Verify that the dictionary contains the new or updated data.
Console.Write("Most recent high temperatures for {0} are: ", cities(ci.Name).Name)
Dim temps = cities(ci.Name).RecentHighTemperatures
For Each temp In temps
Console.Write("{0}, ", temp)
Next
Console.WriteLine()
End Sub
'This method shows how to use data and ensure that it has been
' added to the dictionary.
Private Shared Sub RetrieveValueOrAdd()
Dim searchKey = "Caracas"
Dim retrievedValue As CityInfo = Nothing
Try
retrievedValue = cities.GetOrAdd(searchKey, GetDataForCity(searchKey))
Catch e As ArgumentException
Console.WriteLine(e.Message)
End Try
' Use the data.
If Not retrievedValue Is Nothing Then
Console.WriteLine("Most recent high temperatures for {0} are: ", retrievedValue.Name)
Dim temps = cities(retrievedValue.Name).RecentHighTemperatures
For Each temp In temps
Console.Write("{0}, ", temp)
Next
End If
Console.WriteLine()
End Sub
' This method shows how to retrieve a value from the dictionary,
' when you expect that the key/value pair already exists,
' and then possibly update the dictionary with a new value for the key.
Private Shared Sub RetrieveAndUpdateOrAdd()
Dim retrievedValue As CityInfo = New CityInfo()
Dim searchKey = "Buenos Aires"
If (cities.TryGetValue(searchKey, retrievedValue)) Then
' Use the data
Console.Write("Most recent high temperatures for {0} are: ", retrievedValue.Name)
Dim temps = retrievedValue.RecentHighTemperatures
For Each temp In temps
Console.Write("{0}, ", temp)
Next
' Make a copy of the data. Our object will update its lastQueryDate automatically.
Dim newValue As CityInfo = New CityInfo(retrievedValue.Name,
retrievedValue.Longitude,
retrievedValue.Latitude,
retrievedValue.RecentHighTemperatures)
Else
Console.WriteLine("Unable to find data for {0}", searchKey)
End If
End Sub
' Assume this method knows how to find long/lat/temp info for any specified city.
Private Shared Function GetDataForCity(ByVal searchKey As String) As CityInfo
' Real implementation left as exercise for the reader.
If String.CompareOrdinal(searchKey, "Caracas") = 0 Then
Return New CityInfo() With {.Name = "Caracas",
.Longitude = 10.5,
.Latitude = -66.916667,
.RecentHighTemperatures = {91, 89, 91, 91, 87, 90, 91}}
ElseIf String.CompareOrdinal(searchKey, "Buenos Aires") = 0 Then
Return New CityInfo() With {.Name = "Buenos Aires",
.Longitude = -34.61,
.Latitude = -58.369997,
.RecentHighTemperatures = {80, 86, 89, 91, 84, 86, 88}}
Else
Throw New ArgumentException("Cannot find any data for {0}", searchKey)
End If
End Function
End Class
End Namespace
namespace DictionaryHowTo
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
// The type of the Value to store in the dictionary:
class CityInfo : IEqualityComparer<CityInfo>
{
public string Name { get; set; }
public DateTime lastQueryDate { get; set; }
public decimal Longitude { get; set; }
public decimal Latitude { get; set; }
public int[] RecentHighTemperatures { get; set; }
public CityInfo(string name, decimal longitude, decimal latitude, int[] temps)
{
Name = name;
lastQueryDate = DateTime.Now;
Longitude = longitude;
Latitude = latitude;
RecentHighTemperatures = temps;
}
public CityInfo()
{
}
public CityInfo(string key)
{
Name = key;
// MaxValue means "not initialized"
Longitude = Decimal.MaxValue;
Latitude = Decimal.MaxValue;
lastQueryDate = DateTime.Now;
RecentHighTemperatures = new int[] { 0 };
}
public bool Equals(CityInfo x, CityInfo y)
{
return x.Name == y.Name && x.Longitude == y.Longitude && x.Latitude == y.Latitude;
}
public int GetHashCode(CityInfo obj)
{
CityInfo ci = (CityInfo)obj;
return ci.Name.GetHashCode();
}
}
class Program
{
// Create a new concurrent dictionary.
static ConcurrentDictionary<string, CityInfo> cities = new ConcurrentDictionary<string, CityInfo>();
static void Main(string[] args)
{
CityInfo[] data =
{
new CityInfo(){ Name = "Boston", Latitude = 42.358769M, Longitude = -71.057806M, RecentHighTemperatures = new int[] {56, 51, 52, 58, 65, 56,53}},
new CityInfo(){ Name = "Miami", Latitude = 25.780833M, Longitude = -80.195556M, RecentHighTemperatures = new int[] {86,87,88,87,85,85,86}},
new CityInfo(){ Name = "Los Angeles", Latitude = 34.05M, Longitude = -118.25M, RecentHighTemperatures = new int[] {67,68,69,73,79,78,78}},
new CityInfo(){ Name = "Seattle", Latitude = 47.609722M, Longitude = -122.333056M, RecentHighTemperatures = new int[] {49,50,53,47,52,52,51}},
new CityInfo(){ Name = "Toronto", Latitude = 43.716589M, Longitude = -79.340686M, RecentHighTemperatures = new int[] {53,57, 51,52,56,55,50}},
new CityInfo(){ Name = "Mexico City", Latitude = 19.432736M, Longitude = -99.133253M, RecentHighTemperatures = new int[] {72,68,73,77,76,74,73}},
new CityInfo(){ Name = "Rio de Janiero", Latitude = -22.908333M, Longitude = -43.196389M, RecentHighTemperatures = new int[] {72,68,73,82,84,78,84}},
new CityInfo(){ Name = "Quito", Latitude = -0.25M, Longitude = -78.583333M, RecentHighTemperatures = new int[] {71,69,70,66,65,64,61}}
};
// Add some key/value pairs from multiple threads.
Task[] tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 2; i++)
{
if (cities.TryAdd(data[i].Name, data[i]))
Console.WriteLine("Added {0} on thread {1}", data[i],
Thread.CurrentThread.ManagedThreadId);
else
Console.WriteLine("Could not add {0}", data[i]);
}
});
tasks[1] = Task.Factory.StartNew(() =>
{
for (int i = 2; i < data.Length; i++)
{
if (cities.TryAdd(data[i].Name, data[i]))
Console.WriteLine("Added {0} on thread {1}", data[i],
Thread.CurrentThread.ManagedThreadId);
else
Console.WriteLine("Could not add {0}", data[i]);
}
});
// Output results so far.
Task.WaitAll(tasks);
// Enumerate collection from the app main thread.
// Note that ConcurrentDictionary is the one concurrent collection
// that does not support thread-safe enumeration.
foreach (var city in cities)
{
Console.WriteLine("{0} has been added.", city.Key);
}
AddOrUpdateWithoutRetrieving();
RetrieveValueOrAdd();
RetrieveAndUpdateOrAdd();
Console.WriteLine("Press any key.");
Console.ReadKey();
}
// This method shows how to add key-value pairs to the dictionary
// in scenarios where the key might already exist.
private static void AddOrUpdateWithoutRetrieving()
{
// Sometime later. We receive new data from some source.
CityInfo ci = new CityInfo() { Name = "Toronto",
Latitude = 43.716589M,
Longitude = -79.340686M,
RecentHighTemperatures = new int[] { 54, 59, 67, 82, 87, 55, -14 } };
// Try to add data. If it doesn't exist, the object ci is added. If it does
// already exist, update existingVal according to the custom logic in the
// delegate.
cities.AddOrUpdate(ci.Name, ci,
(key, existingVal) =>
{
// If this delegate is invoked, then the key already exists.
// Here we make sure the city really is the same city we already have.
// (Support for multiple cities of the same name is left as an exercise for the reader.)
if (ci != existingVal)
throw new ArgumentException("Duplicate city names are not allowed: {0}.", ci.Name);
// The only updatable fields are the temerature array and lastQueryDate.
existingVal.lastQueryDate = DateTime.Now;
existingVal.RecentHighTemperatures = ci.RecentHighTemperatures;
return existingVal;
});
// Verify that the dictionary contains the new or updated data.
Console.Write("Most recent high temperatures for {0} are: ", cities[ci.Name].Name);
int[] temps = cities[ci.Name].RecentHighTemperatures;
foreach (var temp in temps) Console.Write("{0}, ", temp);
Console.WriteLine();
}
// This method shows how to use data and ensure that it has been
// added to the dictionary.
private static void RetrieveValueOrAdd()
{
string searchKey = "Caracas";
CityInfo retrievedValue = null;
try
{
retrievedValue = cities.GetOrAdd(searchKey, GetDataForCity(searchKey));
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
}
// Use the data.
if (retrievedValue != null)
{
Console.Write("Most recent high temperatures for {0} are: ", retrievedValue.Name);
int[] temps = cities[retrievedValue.Name].RecentHighTemperatures;
foreach (var temp in temps) Console.Write("{0}, ", temp);
}
Console.WriteLine();
}
// This method shows how to retrieve a value from the dictionary,
// when you expect that the key/value pair already exists,
// and then possibly update the dictionary with a new value for the key.
private static void RetrieveAndUpdateOrAdd()
{
CityInfo retrievedValue;
string searchKey = "Buenos Aires";
if (cities.TryGetValue(searchKey, out retrievedValue))
{
// use the data
Console.Write("Most recent high temperatures for {0} are: ", retrievedValue.Name);
int[] temps = retrievedValue.RecentHighTemperatures;
foreach (var temp in temps) Console.Write("{0}, ", temp);
// Make a copy of the data. Our object will update its lastQueryDate automatically.
CityInfo newValue = new CityInfo(retrievedValue.Name,
retrievedValue.Longitude,
retrievedValue.Latitude,
retrievedValue.RecentHighTemperatures);
// Replace the old value with the new value.
if (!cities.TryUpdate(searchKey, retrievedValue, newValue))
{
//The data was not updated. Log error, throw exception, etc.
Console.WriteLine("Could not update {0}", retrievedValue.Name);
}
}
else
{
// Add the new key and value. Here we call a method to retrieve
// the data. Another option is to add a default value here and
// update with real data later on some other thread.
CityInfo newValue = GetDataForCity(searchKey);
if( cities.TryAdd(searchKey, newValue))
{
// use the data
Console.Write("Most recent high temperatures for {0} are: ", newValue.Name);
int[] temps = newValue.RecentHighTemperatures;
foreach (var temp in temps) Console.Write("{0}, ", temp);
}
else
Console.WriteLine("Unable to add data for {0}", searchKey);
}
}
//Assume this method knows how to find long/lat/temp info for any specified city.
static CityInfo GetDataForCity(string name)
{
// Real implementation left as exercise for the reader.
if (String.CompareOrdinal(name, "Caracas") == 0)
return new CityInfo() { Name = "Caracas",
Longitude = 10.5M,
Latitude = -66.916667M,
RecentHighTemperatures = new int[] { 91, 89, 91, 91, 87, 90, 91 } };
else if (String.CompareOrdinal(name, "Buenos Aires") == 0)
return new CityInfo() { Name = "Buenos Aires",
Longitude = -34.61M,
Latitude = -58.369997M,
RecentHighTemperatures = new int[] { 80, 86, 89, 91, 84, 86, 88 } };
else
throw new ArgumentException("Cannot find any data for {0}", name);
}
}
}
ConcurrentDictionary<TKey, TValue>foi projetado para cenários de multithreaded. Você não precisará usar bloqueios em seu código para adicionar ou remover itens da coleção. Entretanto, sempre é possível que um segmento para recuperar um valor e outro segmento para atualizar imediatamente a coleção fornecendo um novo valor para a mesma chave.
Além disso, embora todos os métodos de ConcurrentDictionary<TKey, TValue> são thread-safe, nem todos os métodos são especificamente atômicos, GetOrAdd e AddOrUpdate. O delegado de usuário que é passado para um desses métodos é chamado fora do bloqueio de interno do dicionário. (Isso é feito para impedir a todos os threads de bloqueio de código desconhecido). Portanto é possível que essa seqüência de eventos ocorra:
1) chamadas de threadA GetOrAdd, não localiza nenhum item e cria um novo item para adicionar invocando o representante de valueFactory.
2) chamadas de threadB GetOrAdd seu delegado valueFactory é chamado e ele chega o bloqueio interno antes de threadA, simultaneamente, e então o seu novo par chave-valor é adicionado ao dicionário.
3) representante do usuário de de threadA é concluída e o thread chega o bloqueio, mas agora vê o item já existe
4) threadA realiza um "Get" e retorna os dados que tenha sido adicionados anteriormente por threadB.
Portanto, não é garantido que os dados que são retornados por GetOrAdd são os mesmos dados que foi criados por valueFactory de. o segmento Uma seqüência semelhante de eventos pode ocorrer quando AddOrUpdate é chamado.