Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/294.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/cmake/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在c#中执行线程安全函数记忆?_C#_.net_Multithreading_Memoization - Fatal编程技术网

如何在c#中执行线程安全函数记忆?

如何在c#中执行线程安全函数记忆?,c#,.net,multithreading,memoization,C#,.net,Multithreading,Memoization,在堆栈溢出中,我有一段代码,用于记忆单参数函数: static Func<A, R> Memoize<A, R>(this Func<A, R> f) { var d = new Dictionary<A, R>(); return a=> { R r; if (!d.TryGetValue(a, out r)) { r = f(a);

在堆栈溢出中,我有一段代码,用于记忆单参数函数:

static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=> 
    {
        R r;
        if (!d.TryGetValue(a, out r))
        {
            r = f(a);
            d.Add(a, r);
        }
        return r;
    };
}
静态函数记忆(此函数)
{
var d=新字典();
返回a=>
{
R;
如果(!d.TryGetValue(a,out r))
{
r=f(a);
d、 加(a,r);
}
返回r;
};
}
虽然这段代码为我完成了它的工作,但当同时从多个线程调用memorized函数时,它有时会失败:
Add
方法使用相同的参数调用两次并抛出异常

如何使备忘录线程安全?

您可以使用它完成所需的一切:

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

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

static Func,但需要注意的是,它们不够紧凑,需要使用锁。

就像Gman提到的
concurrentdirectionary
是实现这一点的首选方法,但是如果一个简单的
lock
语句无法实现这一点就足够了

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;
    };
}
静态函数记忆(此函数)
{
var d=新字典();
返回a=>
{
R;
锁(d)
{
如果(!d.TryGetValue(a,out r))
{
r=f(a);
d、 加(a,r);
}
}
返回r;
};
}
使用锁而不是
ConcurrentDictionary
的一个潜在问题是,这种方法可能会在程序中引入死锁

  • 您有两个记忆化函数
    \u memo1=Func1.Memoize()
    \u memo2=Func2.Memoize()
    ,其中
    \u memo1
    \u memo2
    是实例变量
  • Thread1调用
    \u memo1
    Func1
    开始处理
  • Thread2调用
    \u memo2
    ,在
    Func2
    内部调用
    \u memo1
    和Thread2块
  • Thread1对
    Func1
    的处理在函数的后期调用
    \u memo2
    ,Thread1阻塞
  • 死锁 因此,如果可能,请使用
    ConcurrentDictionary
    ,但如果您不能使用,而是使用锁,请不要调用其他范围在您正在运行的函数之外的记忆函数(当在记忆函数内部时),否则您将面临死锁的风险(如果
    \u memo1
    \u memo2
    是局部变量而不是实例变量,则不会发生死锁)


    (注意,使用
    ReaderWriterLock
    可能会略微提高性能,但仍然会出现相同的死锁问题。)

    使用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;
    }
    
    Dictionary\u description=newdictionary();
    公共浮点getDescription(字符串值)
    {
    字符串查找;
    if(_description.TryGetValue(id,out查找)){
    返回查找;
    }
    _描述[id]=值;
    返回查找;
    }
    
    进一步展开,我想用一个以上的参数来记忆一个函数。下面是我是如何做到的,使用一个C#(需要C#7)作为
    ConcurrentDictionary
    的键

    这项技术可以很容易地扩展到允许更多的参数:

    public static class FunctionExtensions
    {
        // Function with 1 argument
        public static Func<TArgument, TResult> Memoize<TArgument, TResult>
        (
            this Func<TArgument, TResult> func
        )
        {
            var cache = new ConcurrentDictionary<TArgument, TResult>();
    
            return argument => cache.GetOrAdd(argument, func);
        }
    
        // Function with 2 arguments
        public static Func<TArgument1, TArgument2, TResult> Memoize<TArgument1, TArgument2, TResult>
        (
            this Func<TArgument1, TArgument2, TResult> func
        )
        {
            var cache = new ConcurrentDictionary<(TArgument1, TArgument2), TResult>();
    
            return (argument1, argument2) =>
                cache.GetOrAdd((argument1, argument2), tuple => func(tuple.Item1, tuple.Item2));
        }
    }
    
    公共静态类函数扩展
    {
    //带1个参数的函数
    公共静态函数记忆
    (
    这个函数
    )
    {
    var cache=新的ConcurrentDictionary();
    返回参数=>cache.GetOrAdd(参数,func);
    }
    //具有2个参数的函数
    公共静态函数记忆
    (
    这个函数
    )
    {
    var cache=新的ConcurrentDictionary();
    返回(argument1、argument2)=>
    GetOrAdd((argument1,argument2),tuple=>func(tuple.Item1,tuple.Item2));
    }
    }
    
    例如:

    Func<int, string> example1Func = i => i.ToString();
    var example1Memoized = example1Func.Memoize();
    var example1Result = example1Memoized(66);
    
    Func<int, int, int> example2Func = (a, b) => a + b;
    var example2Memoized = example2Func.Memoize();
    var example2Result = example2Memoized(3, 4);
    
    Func example1Func=i=>i.ToString();
    var example1Memoized=example1Func.Memoize();
    var example1Result=Example1emoided(66);
    Func example2Func=(a,b)=>a+b;
    var example2Memoized=example2Func.Memoize();
    var example2Result=Example2emonized(3,4);
    

    (当然,为了获得记忆化的好处,您通常希望将
    example1emotized
    /
    example2emotized
    保存在类变量中或它们不会短暂存在的地方).

    请注意,
    GetOrAdd
    不会完全阻止对给定参数多次调用f;它只保证只将一个调用的结果添加到字典中。如果线程在添加缓存值之前同时检查缓存,则可以获得多个调用。这是通常不值得担心这一点,但我提到它是为了防止调用产生不必要的副作用。@JamesWorld是的,没错。编辑的答案反映了这一点,谢谢!我有点困惑-
    cache
    这里不是一个局部变量吗?每次
    ThreadsafeMemoize()
    被调用,它不会创建一个新的字典吗?@dashnick是的,但您提供了一个函数,然后返回了一个函数。因此,要记忆每个函数,您只需调用一次
    ThreadsafeMemoize
    ,然后使用返回的函数代替您放入的函数。这样,每个记忆函数都会创建一次缓存。如果要使用多个参数记忆函数,您可能会感兴趣。