C# Singleton-Pattern 在实现并行而不是并发处理后无法按预期工作

C# Singleton-Pattern not working as expected after implementing parallel instead of concurrent processing

免责声明:我知道像 Thread Safe C# Singleton Pattern 这样的问题,但是这些并没有回答我的问题。

让我首先描述我的 C# 项目来说明我的问题: 我有一个 class JobProcessor 接受 class JobTicket 的对象,它包含有关 "what to do" 的信息。 JobProcessor 根据 JobTicket 中的参数使用 Facade Pattern 来协调其他 classes,例如关于那个作业的 .log 文件的路径。为此,我有一个 Singleton class LoggerJobProcessor 在作业开始时为其设置路径,然后每个其他 class 将调用 Logger.Log(message).在我达到使用接受 class JobTicket 的对象并将它们保存在队列中的 class JobManager 实现并行化之前,这工作得很好。 JobManager 从其队列中用 JobTicket 实例化一个新的 JobProcessor。并联最多 4 个。

现在您已经可以想象发生了什么:如果同时运行多个 JobProcessor,其中一个会覆盖 Logger 的路径。如何在不更改大量代码的情况下确保 Logger 包含在 JobProcessor 中?我考虑过在 JobProcessor class 中有一个 Logger 字段,但是我必须将它传递给所有其他想要使用它的 class,因为通过 Facade pattern 门面知道它使用的每个 class,但是它使用的每个 class 不知道门面。

事后看来,记录器的单例模式并不像想象的那么聪明,但将其传递给每个 class 似乎很乏味。有更聪明的方法吗?每个线程可以有一个单例吗?

public static class JobManager
{
    private static BlockingCollection<JobTicket> _jobs = new BlockingCollection<JobTicket>();

    public static void AddJob(JobTicket job)
    {
        _jobs.Add(job);
    }

    public static void StartConsumer()
    {
        Task.Factory.StartNew(() =>
        {
            void processJob()
            {
                while (!_jobs.IsCompleted)
                {
                    var job = _jobs.Take();
                    try
                    {
                        using (JobProcessor processor = new JobProcessor(job))
                        {
                            processor.Start();
                        }
                    }
                    catch
                    {
                        // Alert something that an error happened
                    }
                }
            };
            // process 4 jobs in parallel
            Parallel.Invoke(processJob, processJob, processJob, processJob); 
        });
    }
}

public class JobProcessor
{
    private JobTicket jobticket;

    public JobProcessor(JobTicket jobticket) {
        this.jobticket = jobticket;
        // ...
    }

    public void Start() {
        if(!(jobticket.LogPath is null)) {
            var logger = new TextLogger(jobticket.LogPath);
            Logger.SetLogger(logger);
        }
        Logger.Log("Job started");
        // Process job
        var a = new ClassA(...);
        if (jobticket.x)
            a.DoSomething();
        else
            a.DoSomethingElse();
    }
}

public class ClassA {
    //...
    public void DoSomething() {
        //...
        Logger.Log("I did something");
    }
    public void DoSomethingElse() {
        //...
        Logger.Log("I did something else");
    }
}

// I know this is not thread-safe, but what I want is one Logger instance per JobProcessor and not one Logger instance for all JobProcessors.
public static class Logger
{
    private static BaseLogger _logger = new ConsoleLogger();

    public static void Log(string message) {
        _logger.Log(message);
    }

    public static void SetLogger(BaseLogger logger)
    {
        _logger = logger;
    }
}

public abstract class BaseLogger
{
    public abstract void Log(string message);
}

public class TextLogger : BaseLogger
{
    public readonly string path;
    public TextLogger(string path) : base()
    {
        this.path = path;
    }

    public override void Log(string message)
    {
        File.AppendAllText(path, message);
    }
}

public class ConsoleLogger : BaseLogger
{
    public override void Log(string message)
    {
        Console.WriteLine(message);
    }
}

您可以在静态记录器 class 中创建某种 Dictionary<ThreadId, BaseLogger>。您将为每个线程拥有自己的记录器。

此外,您可以将 SetLogger 签名更改为类似 void SetLogger(Func<BaseLogger> loggerFactory) 的 smth,以在 Logger.

中创建您需要的记录器数量

根据评论,似乎 ThreadStaticAttribute 原来是答案。

根据 ThreadStaticAttribute 的文档:

Indicates that the value of a static field is unique for each thread.

因此使用该属性标记 Logger._logger 应该使每个实例对于每个线程都是唯一的。