How to perform thread-safe function memoization in c#?

笑着哭i 提交于 2019-11-28 10:55:39
Gman

You can use ConcurrentDictionary.GetOrAdd which does everything you need:

static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f)
{
    var cache = new ConcurrentDictionary<A, R>();

    return argument => cache.GetOrAdd(argument, f);
}

The function f should be thread-safe itself, because it can be called from multiple threads simultaneously.

This code also doesn't guarantee that function f is called only once per unique argument value. It can be called many times, in fact, in the busy environment. If you need this kind of contract, you should take a look at the answers in this related question, but be warned that they're not as compact and require using locks.

Like Gman mentioned ConcurrentDictionary is the preferred way to do this, however if that is not available to a simple lock statement would suffice.

static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=> 
    {
        R r;
        lock(d)
        {
            if (!d.TryGetValue(a, out r))
            {
                r = f(a);
                d.Add(a, r);
            }
        }
        return r;
    };
}

One potential issue using locks instead of ConcurrentDictionary is this method could introduce deadlocks in to your program.

  1. You have two memoized functions _memo1 = Func1.Memoize() and _memo2 = Func2.Memoize(), where _memo1 and _memo2 are instance variables.
  2. Thread1 calls _memo1, Func1 starts processing.
  3. Thread2 calls _memo2, inside Func2 there is a call to _memo1 and Thread2 blocks.
  4. Thread1's processing of Func1 gets to a call of _memo2 late in the function, Thread1 blocks.
  5. DEADLOCK!

So if at all possible, use ConcurrentDictionary, but if you can't and you use locks instead do not call other Memoized functions that are scoped outside of the function you are running in when inside Memoized functions or you open yourself up to the risk of deadlocks (if _memo1 and _memo2 been local variables instead of instance variables the deadlock would not have happened).

(Note, performance may be slightly improved by using ReaderWriterLock but you still will have the same deadlock issue.)

using System.Collections.Generic;

Dictionary<string, string> _description = new Dictionary<string, string>();
public float getDescription(string value)
{
     string lookup;
     if (_description.TryGetValue (id, out lookup)) {
        return lookup;
     }

     _description[id] = value;
     return lookup;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!