保证一个类只能被实例化它的线程使用

3

我创建了一个不安全的类,如果假定为线程安全,则可能导致严重错误。虽然我正在努力使我的类成为线程安全,但我希望将实例限制为仅可由一个线程使用。目前,我的实现方式是在每个暴露点检查当前线程是否与用于构造实例的线程相同。

public class NotThreadSafeClass
{
    private readonly int _creatorThreadId;

    public NotThreadSafeClass()
    {
        _creatorThreadId = Thread.CurrentThread.ManagedThreadId;
    }

    public string ExposedProp
    {
        get
        {
            AssertSameThread();
        return "My Prop";
        }
    }

    public void ExposedMethod()
    {
        AssertSameThread();
        /* Do stuff */
    }

    private void AssertSameThread()
    {
        Throw.If(_creatorThreadId != Thread.CurrentThread.ManagedThreadId,
                 @"NotThreadSafeClass is not thread safe.  Please don't use
           the same instance of NotThreadSafeClass in multiple threads.");
    }
}

注意:Throw.If定义在http://www.codeducky.org/10-utilities-c-developers-should-know-part-one/中。

这种模式似乎有效,但如果开发人员忘记将此检查添加到任何新的暴露中,则容易出现错误。有没有一种更安全和/或更优雅的方法来确保实例仅被一个线程使用?


只是好奇...为什么只有创建类的线程才能使用它很重要呢? - Bryan Crosby
你是指类似 ThreadLocal<T> 这样的东西吗? - jdphenix
2
你的非泛型重写Throw.If是有害的,因为它会留下一个不明确的异常。而且这也不是正确的复用级别。将ThreadId检查和抛出(一个适当描述的异常)放在一个帮助函数中,那么至少进行检查的负担会更轻,因此更不可能被跳过。 - Ben Voigt
@BenVoigt,好的提醒。实际上我正在使用一个帮助函数,它使用Throw.If来抛出更具体的异常消息。我只是为了简化我的示例而排除了那个帮助函数。我会修改我的示例。 - Steven Wexler
@BryanCrosby 我正在编写一个与 NUnit 交互的组件。我不太相信我的类和 NUnit 之间的交互是否正常工作,因为当我尝试在不同的线程中使用该组件时,我的单元测试会显示出意外的行为。 - Steven Wexler
Luc Morin的回答是最好的选择,你甚至可以尝试使用RealProxy类(http://msdn.microsoft.com/en-us/library/system.runtime.remoting.proxies.realproxy(v=vs.100).aspx)。 - Alessandro D'Andria
2个回答

2
我认为除非使用AOP框架,否则您将不得不在自己的代码中“拦截”对类方法/属性的所有这种访问,就像您所描述的那样。
我想到了Ninject的Interception ExtensionPostSharp
语言/框架本身没有内置此功能。
干杯

1

编辑:将 ThreadLocal<T> 移动到类声明内的私有字段。

除非我完全误解,ThreadLocal<T> 应该可以满足您的需求。例如:

class Foo {
  private ThreadLocal<int> _internalState;

  public Foo() {
    _internalState = new ThreadLocal<int>();
  }

  public int IntValue {
    get { return _internalState.Value; }
    set { _internalState.Value = value; }
  }

  public override string ToString() {
    return _internalState.ToString();
  }
}

class Program {
  public static void Main(string[] args) {
    Demonstrate();
  }

  static void Demonstrate() {
    var local = new Foo {IntValue = 5};
    Console.WriteLine("Start thread value: {0}", local.IntValue);

    new Thread(() => {
      local.IntValue += 5;
      Console.WriteLine("New thread value: {0}", local.IntValue);
    }).Start();

    local.IntValue += 10;
    Console.WriteLine("Start thread value: {0}", local.IntValue);
  }
}

样例输出:

Start thread value: 5
Start thread value: 15
New thread value: 5

@RobertHarvey 这不是。 - jdphenix
这很棒,我认为它可以让我完成90%的工作!我想在第一次实例化类时抛出异常。使用闭合布尔值应该很容易实现这个功能。我需要进行一些重构来实现这个功能,但我认为这对我来说应该可行。谢谢! - Steven Wexler
@StevenWexler ThreadLocal 没有被密封,因此您可能能够修改它的行为以满足您的需求 - 不确定。 - jdphenix

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