ConcurrentDictionary – metody GetOrAdd a AddOrUpdate

Tomáš Jecha       14. 11. 2012       C#       13486 zobrazení

Už před více jak rokem jsem psal do služby Microsoft Connect požadavek, aby byl přidán dodatek do dokumentace k třídě slovníku optimalizovaného do více vláknového prostředí - ConcurrentDictionary. Bylo potřeba upřesnit, jakým způsobem se chovají některé z jeho metod právě při volání z více vláken simultálně. Před nějakou dobou jsem se konečně dočkal a dále popisované chování bylo upřesněno i v dokumentaci. Jen mi není jasné proč jen v dokumentaci .NET 4.5, když chování jsem reportovat už pro .NET 4, kde stále chybí.

Jedná se o metody:

Tyto metody slouží k operacím pro čtení a úpravu položek z tohoto slovníku. Obě metody přijímají delegáta, který se odkazuje na funkci, která se zavolá ve chvíli, kdy slovník zatím neobsahuje žádnou hodnotu s daným klíčem.

Podívejte se na zkrácenou (nekamenujte mě za nedostatky) ukázku kódu, který používá ConcurrentDictionary k uchování pojmenovaných připojení do databáze. Využil jsem metody GetOrAdd pro získání existujícího připojení a zároveň v případě, že připojení neexistuje, vytváří nové metodou CreateNamedConnection. Všechny připojení následně zruším pomocí Dispose metody.

Když jsem tyto metody viděl poprvé, čekal jsem, že se delegát pro vytvoření nové hodnoty zavolá pouze jednou. Ve skutečnost se však může zavolat vícekrát, ale jeho výsledek se použije pouze z jednoho volání. To právě popisuje nově přidaný dodatek z MSDN:

If you call GetOrAdd/AddOrUpdate simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call.

Toto může být nepříjemný problém, pokud volání delegáta trvá delší dobu a buď zatěžuje systémové prostředky / přistupuje k not-thread-safe zdrojům a nebo vytváří unmanaged zdroje.

Vše totiž nasvědčuje tomu, že pokud jiné vlákno zavolá znovu jednu z těchto metod v průběhu zpracování kódu delegáta jiného vlákna, ConcurrentDictionary i na tomto novém vlákno delegáta zavolá, ačkoliv nakonec hodnotu, kterou vrátí, nepoužije. Je to protože předchozí vlákno stihne doběhnout a do slovníku se hodnota mezitím přidá pouze jednou.

To demonstruje právě následující kód, který ve většině případů vytvoří vyvoláním delegáta více instancí připojení do databáze, reálně se ale použije jen jedna a v paměti zůstane po nějakou dobu otevřené další nepoužívané a zbytečné připojení. V praxi to tedy znamená, že není vhodné použít tyto metody pro řízení vytváření jen jedné instance pro daný klíč.

static void Main(string[] args)
{
    var dictionary = new ConcurrentDictionary<string, SqlConnection>();

    // create threads
    Thread[] threads = new Thread[20];
    for (int i = 0; i < threads.Length; i++)
    {
        threads[i] = new Thread(() => { ThreadStartMethod(dictionary); });
        threads[i].Start();
    }

    // wait for all threads
    foreach (var thread in threads)
    {
        thread.Join();
    }

    // dispose all items in dictionary
    foreach (var item in dictionary)
    {
        item.Value.Dispose();
        Console.WriteLine("Connection disposed for key \"{0}\".", item.Key);
    }

    Console.ReadLine();
}

static void ThreadStartMethod(ConcurrentDictionary<string, SqlConnection> dictionary)
{
    SqlConnection connection = dictionary.GetOrAdd("ConnectionName", CreateNamedConnection);
    // do something with connection
}

private static SqlConnection CreateNamedConnection(string key)
{
    Console.WriteLine("New connection opened for key \"{0}\".", key);
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); // simulate opening connection
    return new SqlConnection("Server=.");
}

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback