如何使某些代码只运行一次?

4
同一程序集中有两个类:类A静态类B
类A的一个方法中,我调用了类B的一个方法,但我希望它只被调用一次,而不是每次调用类A的方法时都被调用...目前我设置了一个全局属性-Ran-来查看该方法是否已经运行过了...虽然它能够工作,但我感觉这不是最好的设计。我想知道是否有更好的方法来实现同样的功能?
谢谢。
ClassA.MyMethod(...)
{
....
//.... 
if (ClassB.Ran != 1)
    ClassB.Foo();
....
//...
}

1
那可能是最好的设计。我不清楚你还在寻找什么。 - Jonathan Wood
5个回答

9

在解释“只运行一次”限制的严格程度时,您需要小心。通常假定静态方法是线程安全的;如果您确实希望确保即使在竞争线程的情况下,您的方法也仅运行一次,则需要使用同步机制,最简单的示例是使用 lock

private static bool isRun = false;
private static readonly object syncLock = new object();

public void MyMethod()
{
    lock (syncLock)
    {
        if (!isRun)
        {
            Foo();            
            isRun = true;
        }
    }
}

这是 Lazy<T> 的一个非常好的特性,它可以为您处理这个细节。 - asawyer
1
@asawyer:同意,我通常更喜欢使用Lazy<T>进行字段初始化。然而,给定的示例并没有表明正在初始化任何特定字段。仅为了调用初始化方法作为副作用而调用属性getter,而不返回任何重要值,这是不直观的。 - Douglas
@Douglas 可以考虑在锁之前加上 if(isRun) return;,以节省长期的线程开销。 - EtherDragon
@EtherDragon:我曾经简单提到过双重检查锁定及其危害 在另一条评论中。简短回答:除非你知道自己在做什么,否则不要使用非阻塞同步。如果你引入了所提出的快捷方式,几乎肯定还需要引入内存屏障。 - Douglas

8

我通常会使用Lazy<T>来完成这个任务。

考虑以下内容:

public static class Test // this is your ClassB
{
    private static Lazy<string> m_Property = new Lazy<string>( ()=>
    {
        Console.WriteLine("Lazy invoked"); 
        return "hi";
    },true);
    public static string Property
    {
        get
        {
            Console.WriteLine("value request");
            return m_Property.Value;
        }
    }
}

//....consuming code, this is in your ClassA somewhere

var test1 = Test.Property; // first call, lazy is invoked
var test2 = Test.Property; // cached value is used, delgate not invoked
var test3 = Test.Property; // cached value is used, delgate not invoked
var test4 = Test.Property; // cached value is used, delgate not invoked
var test5 = Test.Property; // cached value is used, delgate not invoked

这是输出结果:
value request
Lazy invoked
value request
value request
value request
value request

1
@BDotA 我为你在上面的评论中添加了一些澄清。 - asawyer
@asawyer:谢谢。有趣。我不知道Lazy<T>是.NET中的东西...很好。 - Bohn
@Douglas:非常好,感谢您。有一个小问题:我们在笔记本电脑上使用.NET 4.0和安装了VS2010进行开发,但是为了生产,他们将代码编译为.NET 3.5的构建目标,这是否意味着由于此限制我不能使用它? - Bohn
他们应该将其重命名为LazyButCool<T>。 - user915331
2
在内部,Lazy<T>LazyThreadSafetyMode.ExecutionAndPublication 下运行时使用 双重检查锁定。请注意,如果您不熟悉 缓存一致性 的概念,则很容易出错。 - Douglas
显示剩余3条评论

5
你可以在类上使用一个私有静态字段,以便在第一次调用方法时设置结果,防止其再次运行:
class B
{
  private static bool fooRun = false;

  public static void Foo()
  {
    if (fooRun) { return; }
    ...
    fooRun = true;
  }
}

@BDotA,你不希望其他类检查ClassB中的代码是否运行;这会导致糟糕的面向对象编程和难以维护的代码。将该检查集中在ClassB中,使得ClassB成为其自身状态的唯一授权方,就像Adam V所建议的那样。 - EtherDragon
@EtherDragon:是的,像Adam在ClassB中那样集中处理是更好的想法。谢谢。 - Bohn

1

只需将一个布尔值传递到构造函数中,以确定是否要运行该项?类似这样:

ClassA(bool runA)
{
    if (runA) 
    {
        ClassB.Foo();
    }
}

这不是正确的做法。如果调用者知道被调用方是否应该执行函数,当答案是否定时,它应该简单地不进行调用。向一个带有“开玩笑”的附注的对象发送“做某事”的消息真的很奇怪。 - Tim
1
我假设他总是想要一个 A 的实例,即使那个函数没有运行(他可能正在设置其他在示例中省略的类成员)。你怎么能这么确定呢? - Mathew Thompson
1
实际上你可以,重新阅读问题。条件逻辑在ClassA内部,因此假设被调用者知道是否执行是一个合理的假设。绝对不值得贬低 :) - Mathew Thompson
基于那个建议,我投票支持你的答案作为解决问题的潜在方案。据我所知,这就是downvote的目的。我并不是在批评你,也不是有意无礼 :/ - Tim
1
@Tim Nah我不会说我感到冒犯,你提出了一个有价值的观点:)。关于什么情况下应该投反对票,不同的人有不同的意见,我个人只会在答案A)无法编译或B)根本不接近可行解决方案时使用它。如果您认为这应该得到否决票,那么这很公平:)我不会说它很粗鲁。 - Mathew Thompson
显示剩余2条评论

1

没有更多的具体信息,很难说,但听起来像是 Class B 中包含某种初始化逻辑的方法。如果这只是需要在第一次引用 Class B 时发生的事情,那么可以将其放在 Class B 的静态构造函数中。如果 Class B 中的方法仅需要在引用 Class A 时运行,则可以从 Class A 的静态构造函数中调用它。如果它不能被调用直到调用 Class A 的某个方法,并且它应该只从任何地方调用一次,那么我会建议您从方法内部检查 Class B 中的私有静态变量,并立即返回。否则,作为最后的选择,我会说在 Class A 中拥有一个私有静态变量比全局变量更可取。

然而,在所有这些情况下,我会说您正在创建全局状态,这几乎总是表明设计不良。在我看来,这种问题正呼唤着需要一点“依赖注入”的需求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接