【问题标题】:Multiple threads accessing singleton object in VS2010VS2010中多线程访问单例对象
【发布时间】:2010-08-18 06:31:30
【问题描述】:

我正在使用 Visual Studio 2010 编写一个简单的 C#/.NET GUI 应用程序,其中我使用 Logger 类将跟踪/调试信息从项目的所有各种类中写入单个文件。 (见下面的源代码。)

当实例化其对象类型之一时,每个类的构造函数都会将条目写入日志。其中一个类是自定义 GUI 控制器组件(FileAttributesCtl 类),它包含在程序使用的几个 GUI 表单中。

我遇到的问题是创建了两个日志文件,相隔约 200 毫秒。第一个日志文件(仅)包含一条消息,表明已构建 FileAttributesCtl 对象,第二个日志文件包含写入(假定)共享日志文件输出流的所有其他消息。所以每次我执行我的项目代码时,我都会得到两个日志文件。

更奇怪的是,每次我重建我的项目 (F6) 时,都会为 FileAttributesCtl 对象创建一个日志文件,表明这种类型的控制对象实际上是在构建过程中实例化的

这显然与线程有关。如果日志文件不是唯一命名的(即,如果我没有将唯一的日期/时间字符串附加到文件名),我会得到一个异常,表明多个进程(实际上是 VS2010 进程本身)当前正在使用文件。

所以我的问题是:我如何让单例对象实际上是一个对象?

第二个问题是:为什么 VS2010 会这样?

//----------------------------------------
// Logger.cs
class Logger
{
    // Singleton object
    private static Logger   s_logger =
        new Logger("C:/Temp/foo.log");

    public static Logger Log
    {
        get { return s_logger; }
    }

    private TextWriter  m_out;

    private Logger(string fname)
    {
        // Add a date/time suffix to the filename
        fname = ...;

        // Open/create the logging output file
        m_out = new StreamWriter(
            new FileStream(fname, FileMode.Create, FileAccess.Write,
                FileShare.Read));
        m_out.WriteLine(DateTime.Now.ToString(
            "'$ 'yyyy-MM-dd' 'HH:mm:ss.fff"));
    }

    ...
}

//----------------------------------------
// FileAttributesCtl.cs
public partial class FileAttributesCtl: UserControl
{
    private Logger  m_log = Logger.Log;

    public FileAttributesCtl()
    {
        m_log.WriteLine("FileAttributesCtl()");  //Written to first logfile
        InitializeComponent();
    }

    ...
}

//----------------------------------------
// FileCopyForm.cs
public partial class FileCopyForm: Form
{
    private Logger  m_log = Logger.Log;

    public FileCopyForm()
    {
        // Setup
        m_log.WriteLine("FileCopyForm()");       //Written to second logfile

        // Initialize the GUI form
        m_log.WriteLine("FileCopyGui.InitializeComponent()");
        InitializeComponent();
        ...
    }

    ...
}

注意:这与 2009 年 12 月的一个问题非常相似:
Access to singleton object from another thread
但它没有我的问题的答案。

更新

进一步调查表明,VS2010 确实在构建过程中实例化了自定义组件,可能是为了在设计器窗口中呈现它。

另外,确实有两个单独的线程调用Logger 构造函数(每个线程都有不同的ManagedThreadID)。

使用静态类初始化器构造单例对象不起作用;我仍然得到两个日志文件。

分辨率

经过仔细检查,我注意到自定义控件被实例化了两次,并且在两个日志文件中都显示了这一点。

因此,我认为问题完全在于 VS 在执行程序之前实例化了自定义控件对象,这会导致创建第一个日志文件。然后在程序开始正常执行后创建第二个日志文件

因此,第一个日志文件只是构建过程的副作用,与正常程序操作期间执行的多个线程没有任何关系。

显而易见的解决方案是从组件构造函数中删除所有日志文件副作用代码。或者干脆完全忽略第一个日志文件。

【问题讨论】:

  • 你最好下载 log4net 来完成这项工作:)
  • 有谁知道log4net 是否可以解决这个问题?
  • 我不知道免疫但从他们的常见问题解答... log4net 线程安全吗?是的,log4net 是线程安全的。

标签: c# .net multithreading visual-studio-2010 singleton


【解决方案1】:

很可能是 Visual Studio 正在构建您的 UI 组件(以显示在设计器中),并且在此过程中,您的构造函数被调用,这就是您在构建过程中看到该日志文件的原因。

【讨论】:

  • 这听起来很合理,因为当我之前遇到异常时(因为文件名始终相同,没有唯一的后缀),VS 设计器窗口显示错误,无法重绘表单。不过,这似乎是一种非常愚蠢的做法,因为创建裸组件可能会导致副作用,如果还没有创建主程序表单,其中一些可能是有害的。
  • @Loadmaster 设计器可以让您预览表单在应用程序中实例化时的行为。最准确的方法是使用您为实例化指定的代码 - 有什么比向您展示真实的东西更好?
  • @Loadmaster 构造函数不是执行副作用导致操作的正确位置。也就是表单的Load 事件。
  • @Lucas:非表单组件没有 Load 事件(据我所知)。
  • @Loadmaster:我的错误,我的眼睛错过了这些是用户控件的事实。无论如何,UserControls 也有 Load 事件你可以挂钩。
【解决方案2】:

静态数据 + 线程 = 麻烦

您需要同步对单例的访问(以及单例的初始化)。

静态构造函数可能会有所帮助

class Logger
{
    private static Logger
    static Logger()
    {
        s_logger = new Logger("C:/Temp/foo.log");
    }

    // ...

或者更好的是使用日志库 (log4net) 为您处理所有这些内容。

对于导致创建日志的 VS 构建,我并不感到惊讶。它可能是实例化表单以通过反射发现有关表单的信息。

按 cmets 更新

@LoadMaster "静态类初始化器没有 工作。我在日志文件中添加了更多信息 输出包含当前线程的 ManagedThreadID,果然, 有两个不同的线程 ID 创建两个日志文件。”

这很奇怪。每MSDN

类的静态构造函数 在给定的情况下最多执行一次 应用程序域。的执行 静态构造函数由 以下事件中的第一个发生 在应用程序域内:

  • 创建了一个类的实例。
  • 类的任何静态成员 被引用。

您的线程必须已移动 AppDomains,或者您的 sn-ps 中缺少某些代码。

【讨论】:

  • 我不需要一个复杂的日志类,这就是为什么我自己构建了一个非常简单的类。无论如何,我打算将其从最终代码中删除。
  • 因此,您最终将重新实现其中的一半 :)
  • 静态类初始化器不起作用。我在日志文件输出中添加了更多信息以包括当前线程的ManagedThreadID,果然,有两个不同的线程 ID 创建了两个日志文件。
  • 多哈。 (现在)很明显,第一个日志文件(和第一个控制对象实例化)只是构建过程的副产品。见上面我的决议。
  • 你去吧,这就是它通过静态构造函数的方式......多个AppDomain。很高兴你解决了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多