如何确保每个参数组合只执行一次方法逻辑?

时间:2012-01-12 13:13:24

标签: c# performance thread-safety

我正在设计一个类库,它有一堆类型的方法“EnsureXXX”。只要调用代码需要某些东西而不需要特定于参数的初始化,就会调用此方法的思想。它类似于ASP.Net的EnsureChildControls方法,但参数为鉴别符。

前:

public static class SomeUtilityClass {
    public static void EnsureSomething(string arg1, int arg2, object arg3)
    {
        // Logic should be called once for each args combination 
    }
}

public class CallerClass
{
    public void Foo()
    {
        SomeUtilityClass.EnsureSomething("mycustomerid", 4, myData.SomeProperty);
    }
    public void Foo2()
    {
        SomeUtilityClass.EnsureSomething("mycustomerid", 4, myData.SomeProperty);
    }

}

由于这样的模式将在多个地方重用并经常调用,我必须将性能作为目标。我还必须有一个线程安全的方法。

为此,我写了一个小实用程序类:

public sealed class CallHelper
{
    private static readonly HashSet<int> g_YetCalled = new HashSet<int>();
    private static readonly object g_SyncRoot = new object();

    public static void EnsureOnce(Type type, Action a, params object[] arguments)
    {
        // algorithm for hashing adapted from http://stackoverflow.com/a/263416/588868
        int hash = 17;
        hash = hash * 41 + type.GetHashCode();
        hash = hash * 41 + a.GetHashCode();
        for (int i = 0; i < arguments.Length; i++)
        {
            hash = hash * 41 + (arguments[i] ?? 0).GetHashCode();
        }

        if (!g_YetCalled.Contains(hash))
        {
            lock (g_SyncRoot)
            {
                if (!g_YetCalled.Contains(hash))
                {
                    a();
                    g_YetCalled.Add(hash);
                }
            }
        }
    }
}
消费代码如下所示:

public static class Program
{
    static void Main()
    {
        SomeMethod("1", 1, 1);
        SomeMethod("2", 1, 1);
        SomeMethod("1", 1, 1);
        SomeMethod("1", 1, null);

        Console.ReadLine();
    }

    static void SomeMethod(string arg1, int arg2, object arg3)
    {
        CallHelper.EnsureOnce(typeof(Program), ()=>
        {
            Console.WriteLine("SomeMethod called only once for {0}, {1} and {2}", arg1, arg2, arg3);
        }, arg1, arg2, arg3);
    }
}

正如预期的那样输出:

SomeMethod called only once for 1, 1 and 1
SomeMethod called only once for 2, 1 and 1
SomeMethod called only once for 1, 1 and

我有一些与此方法相关的问题:

  1. 我认为我已正确锁定课程以确保线程安全,但我是对的吗?
  2. HashSet<int>和我计算哈希的方法是否正确?我特别想知道null处理是否正确,以及我是否可以通过这种方式“哈希”Action代表。
  3. 我的方法目前仅支持静态方法。如何在没有内存泄漏的情况下移动到实例兼容的方法(将实例添加为鉴别器)?
  4. 有没有办法避免手动将所有参数传递给实用工具方法(只是指定操作),而无需探索堆栈跟踪(因为性能影响)?我担心由于缺少来自外部方法的论据而引入了很多错误。
  5. 提前致谢

1 个答案:

答案 0 :(得分:5)

这基本上是memoize,除了你的函数是无效的。但是,对输入参数进行比较的相同考虑因素仍然有效。

Wes Dyer讨论how to make a generic, multi-argument memoize in this post。一般的想法是将所有参数都转换为匿名类型,并将其用作字典键。

关于使这个线程安全,请考虑.NET already has concurrent collections。您不需要将非并发集合转换为并发集合;只需使用提供的集合。

要使实例方法在没有泄漏的情况下工作,要么保留WeakReference实例,要么在实例本身中存储memoizer的实例,无论哪种方法都适合您。