在多线程C#应用程序中的懒惰单例模式

15

我正在开发一个多线程的C#应用程序,它正在消费一个WCF Web服务。与Web服务的连接将有一个特定的超时时间,我们可以定义超时时间,在此之后连接将关闭。我希望使用单例模式来存储Web服务的连接。我正尝试按以下方式获取实例:

CLazySingleton ins = CLazySingleton.Instance;
string connection = CLazySingleton.abc;

以下是单例类的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LazySingleton
{
    public class CLazySingleton
    {
        private static readonly Lazy<CLazySingleton> _instance
            = new Lazy<CLazySingleton>(() => new CLazySingleton());
        private static readonly object ThreadLock = new object();
        public static string abc;  
        //I will use the service connection object in place of 'abc' in the application
        //assume that 'abc' is storing the connection object    

        private CLazySingleton()
        { }

        public static CLazySingleton Instance
        {
            get
            {   
                if (abc == null)
                {
                    lock (ThreadLock)
                    {
                        //Make the connection
                        abc = "Connection stored in this variable";
                        Console.WriteLine("Connection Made successfully");
                        return _instance.Value;
                    }                    
                }
                else
                {
                    return _instance.Value;
                }
            }
        }
    }
}

我的问题是: 1. 这段代码能够同时处理多个线程尝试获取实例吗?这是目前我最担心的问题。 2. 我能有更好的解决方案吗? 3. 我需要在这里使用“lock”还是使用“Lazy”方法就可以处理多线程尝试获取实例?
任何帮助都将不胜感激。
谢谢!

我对你的方法有一些疑问。为什么需要维护一个连接的 Singleton?如果每个线程都有自己的代理/连接会有问题吗?既然这是一个 Web 服务,如果创建许多连接,我不预见任何问题。--- 能否更好地了解你的“连接”对象是什么类型? - thewpfguy
你不需要锁定。 - Rafa
3个回答

14
根据微软的延迟初始化文档,在“线程安全初始化”部分中提到:
默认情况下,Lazy对象是线程安全的。
考虑到这一点,您的abc字段不需要是静态的。由于您正在使用Lazy<T>来实例化您的单例,因此在CLazySingleton构造函数中初始化连接是安全的。

嗨Richard,感谢您的回复。您提出了一个很好的观点。我也在思考同样的问题。但我的问题是,如果我在构造函数中编写连接逻辑,那么当连接在指定的超时时间后自动过期时,我该如何处理呢?在这种情况下,字段abc将变为null,并且单例类将继续返回null,因为构造函数不会再次调用。我希望我能表达清楚我的观点。 - Anubhav Sharma
我认为在这里使用Singleton模式不是正确的选择,实际上你不需要以这种方式管理你的连接。而且,你确定你正在调用一个Web服务吗?因为你提到的超时是什么类型的? - thewpfguy

5
这段代码能够处理多个线程同时获取实例的情况吗?
在您的场景中,“abc”字段可能会被初始化两次。想象一下,“abc”变量为空的情况。第一个线程将在赋值之前进入“lock”块。第二个线程将在锁之前等待。因此,第一个线程将初始化“abc”,第二个线程将重新初始化它(您对null的检查在锁之外,这就是原因)。但也许这不是您应该担心的事情。
我能有更好的解决方案吗?
是的,您可以。让我在答案的最后一块中描述它。
我需要在这里使用'lock'还是使用Lazy方法来处理多线程尝试获取实例?
在Lazy类中创建Value属性是线程安全的。在您的情况下,我会使用Lazy<>类的IsValueCreated属性的优势。您仍然需要ThreadLock对象。只有一件事情,即一旦访问Lazy<>类的Value属性,IsValueCreated属性将返回true(这就是诀窍;-))
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LazySingleton
{
    public class CLazySingleton
    {
        private static readonly Lazy<CLazySingleton> _instance
            = new Lazy<CLazySingleton>(() => new CLazySingleton());
        private static readonly object ThreadLock = new object();
        public static string abc;  
        //I will use the service connection object in place of 'abc' in the application
        //assume that 'abc' is storing the connection object    

        private CLazySingleton()
        { }

        public static CLazySingleton Instance
        {
            get
            {   
                if (_instance.IsValueCreated)
                {
                    return _instance.Value;
                }
                lock (ThreadLock)
                {
                    if (abc == null)
                    {
                        abc = "Connection stored in this variable";
                        Console.WriteLine("Connection Made successfully");
                    }
                }
                return _instance.Value;
            }
        }
    }
}

5

简单使用ThreadSafetyMode

 Lazy<MergeSort> ty = new Lazy<MergeSort>(LazyThreadSafetyMode.ExecutionAndPublication);

14
Lazy的默认构造函数使用LazyThreadSafetyMode.ExecutionAndPublication,因此默认情况下是线程安全的。 - Phil
谢谢,我一定会尝试的。 - Anubhav Sharma

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