C # Singleton-Pattern не работает должным образом после реализации параллельной, а не параллельной обработки - PullRequest
2 голосов
/ 30 октября 2019

Отказ от ответственности: я знаю о таких вопросах, как Thread Safe C # Singleton Pattern , однако они не отвечают на мой вопрос.

Позвольте мне изложить мою проблему, сначала описав мой C # проект: у меня естькласс JobProcessor, который принимает объект класса JobTicket, который содержит информацию о том, «что делать». JobProcessor использует шаблон фасада для координации других классов в соответствии с параметрами в JobTicket, такими как, например, путь к файлу .log о том же самом задании. Для этого у меня есть класс Singleton Logger, для которого JobProcessor устанавливает путь в начале задания, а затем любой другой класс просто вызывает Logger.Log(message). Это работало нормально, пока я не дошел до реализации параллелизации с классом JobManager, который принимает объекты класса JobTicket и сохраняет их в очереди. JobManager создает новый JobProcessor с JobTicket из своей очереди. И это до 4 параллельно.

Теперь вы уже можете представить, что произошло: если одновременно выполняется более одного JobProcessor, один перезаписывает путь к Logger. Как я могу убедиться, что Logger содержится внутри JobProcessor без изменения большого количества кода? Я думал о наличии поля Logger в классе JobProcessor, но затем мне пришлось передать его каждому другому классу, который хочет его использовать, поскольку по шаблону Facade фасад знает каждый используемый им класс, но каждый класскоторый используется им, не знает фасада.

Оглядываясь назад, я вижу, что шаблон синглтона для логгера был не таким умным, как предполагалось, но передача его каждому классу кажется утомительной. Есть ли умнее? Могу ли я иметь один синглтон на одну нить?

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);
    }
}

Ответы [ 2 ]

1 голос
/ 31 октября 2019

Судя по комментариям, похоже, что ThreadStaticAttribute оказался ответом.

Согласно документации для ThreadStaticAttribute:

Указывает, что значение статического поля уникально для каждого потока.

Таким образом, пометка Logger._logger этим атрибутом должна сделать каждый экземпляр уникальным для каждого потока.

1 голос
/ 30 октября 2019

Вы можете создать сортировку Dictionary<ThreadId, BaseLogger> в своем классе статического регистратора. У вас будет свой логгер для каждого потока.

Кроме того, вы можете изменить SetLogger подпись на что-то вроде void SetLogger(Func<BaseLogger> loggerFactory), чтобы создать необходимое количество регистраторов внутри Logger.

...