[C#] 多线程单例子,分为阻塞型和分阻塞型, 在unity里的应用

在单例中使用多线程时,需要注意以下几点:

  • 线程安全:在多线程环境下,单例对象可能被多个线程同时访问,因此需要确保单例的线程安全,避免出现数据竞争等问题。

  • 对象创建:如果在单例对象的构造函数中启动了新的线程,那么可能会在单例对象还没有完全创建完成时就开始执行线程。因此,在创建单例对象时需要考虑到线程的启动时机,可以使用懒汉式的延迟加载方式,在需要使用单例对象时再进行初始化。

  • 生命周期管理:如果在单例对象中启动了线程,那么需要考虑线程的生命周期管理,避免线程一直运行导致资源泄漏等问题。可以在单例对象的析构函数中停止线程,或者提供额外的接口供外部调用停止线程。

以下是一个在单例中使用多线程的示例代码:


public class Singleton
{
    
    
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    private Thread workerThread;
    private bool stopWorkerThread = false;

    public static Singleton Instance
    {
    
    
        get
        {
    
    
            lock (padlock)
            {
    
    
                if (instance == null)
                {
    
    
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }

    private Singleton()
    {
    
    
        workerThread = new Thread(WorkerThreadMethod);
        workerThread.Start();
    }

    private void WorkerThreadMethod()
    {
    
    
        while (!stopWorkerThread)
        {
    
    
            // Do some work...
        }
    }

    public void StopWorkerThread()
    {
    
    
        stopWorkerThread = true;
    }

    ~Singleton()
    {
    
    
        StopWorkerThread();
    }
}

在这个例子中,Singleton 是一个单例类,它在构造函数中启动了一个工作线程,并且提供了一个 StopWorkerThread 接口用于停止工作线程。在 Singleton 的析构函数中会调用 StopWorkerThread 接口来停止工作线程,确保线程的生命周期管理。在使用 Singleton 时,可以通过 Singleton.Instance 来获取单例对象,并且可以调用 StopWorkerThread 接口来停止工作线程。

非阻塞型

将GetInstance()的返回类型从Task改为UniTask,这是Unity针对异步编程所提供的更高效的API。
将_instance声明为UniTaskCompletionSource类型,并在Initialize()方法完成后使用TrySetResult()方法将结果赋值给_instance。
在Instance属性中使用AsyncLazy类型来实现延迟初始化,并确保多个线程安全地访问单例。
下面是修改后的代码示例:

using Cysharp.Threading.Tasks;

public abstract class SingletonTask<T> where T : SingletonTask<T>, new()
{
    
    
    private static readonly AsyncLazy<T> _instance = new AsyncLazy<T>(async () =>
    {
    
    
        var instance = new T();
        await instance.InitializeAsync();
        return instance;
    });

    public static UniTask<T> InstanceAsync => _instance.Value;

    protected virtual UniTask InitializeAsync()
    {
    
    
        return UniTask.CompletedTask;
    }
}

这里我们使用了AsyncLazy来延迟初始化单例,并将Initialize()方法改为InitializeAsync(),返回UniTask类型。注意到InitializeAsync()方法是虚方法,方便子类进行实现。

使用时,可以通过调用InstanceAsync属性来获取单例,例如:


public class GameManager : SingletonTask<GameManager>
{
    
    
    private int _score = 0;

    public void AddScore(int score)
    {
    
    
        _score += score;
    }

    protected override UniTask InitializeAsync()
    {
    
    
        Debug.Log("Game manager initialized.");
        return UniTask.CompletedTask;
    }
}

// 在其他地方获取GameManager单例
GameManager.InstanceAsync.ContinueWith(gameManager => {
    
    
    gameManager.AddScore(100);
});

这里通过ContinueWith方法来在获取单例后执行添加分数的操作,而不需要等待单例初始化完成。

阻塞型

如果需要等待单例初始化完成,可以在获取单例的时候返回一个 Task 对象,并在单例初始化完成后 Task 对象得到通知。具体的实现可以参考下面的代码:


public abstract class SingletonTask<T> where T : SingletonTask<T>, new()
{
    
    
    private static readonly object padlock = new object();
    private static T _instance;
    private static TaskCompletionSource<T> _tcs;

    public static async Task<T> GetInstanceAsync()
    {
    
    
        if (_instance != null)
        {
    
    
            return _instance;
        }

        if (_tcs == null)
        {
    
    
            _tcs = new TaskCompletionSource<T>();
        }

        await _tcs.Task;

        return _instance;
    }

    protected SingletonTask()
    {
    
    
        lock (padlock)
        {
    
    
            if (_instance != null)
            {
    
    
                throw new InvalidOperationException("Cannot create multiple instances of singleton.");
            }

            _instance = this as T;
            _tcs?.TrySetResult(_instance);
        }
    }

    public abstract Task Initialize();
}

在上面的代码中,GetInstanceAsync 方法返回一个 Task 对象,如果单例已经初始化完成,则直接返回单例;否则创建一个 TaskCompletionSource 对象 _tcs 并返回其 Task 属性。当单例初始化完成时,调用 _tcs.TrySetResult(_instance) 方法,通知等待该 Task 的代码,单例已经初始化完成。具体使用方式如下:


public class MySingleton : SingletonTask<MySingleton>
{
    
    
    private MySingleton()
    {
    
    
    }

    public override async Task Initialize()
    {
    
    
        // 初始化代码
        await Task.Delay(1000);
    }
}

// 获取单例,并等待初始化完成
var instance = await MySingleton.GetInstanceAsync();

上述代码会等待 MySingleton 的初始化完成,然后返回单例对象。

猜你喜欢

转载自blog.csdn.net/hhh314159/article/details/129325060