0

Имеется программа Windows service, написанная на C#, которая периодически получает набор данных у стороннего web-cервиса, обрабатывает их и записывает в базу данных. Изначально не подразумевалась последовательная загрузка массивов данных. Сначала грузится один набор, потом следующий и т.д. Но со временем объём загружаемых данных и кол-во наборов увеличилось и возникла необходимость загружать их в параллельном режиме. Загрузка одного набора данных стала занимать более часа. Если говорить вкратце, то требуется запуск метода одного и того же класса с различными параметрами в многопоточном режиме. Каждый поток должен запускаться в изолированном режиме и не должен каким-либо образом влиять на одновременное выполнение других аналогичных потоков. Параллельно запускаемые потоки никак между собой не взаимодействуют. Т.е. необходимо чтобы каждый поток работал только со своими экземплярами данных и никак не мог испортить значения переменных, используемых другим потоком.

Грубо говоря есть некий класс DataMultiLoader, содержащий метод Load_and_Export

public class DataMultiLoader
{
                public DataMultiLoader(ServiceLogger p_svLogger)
                {
                    svLogger = p_svLogger;
                    cMTExporter = new Farelogix.MTExport.FQ_Exporter();
                }
            public void Load_and_Export(eProviders p_Provider, int p_nRRKey, RoutesLoadScheduler p_RScheduler)
            {   
            // здесь выполняется много работы с вызовом методов других классов, в том числе и статических
                ...
            }

    }


Отдельная процедура LoadMultiThreading, которая вызывается по таймеру, должна запускать Load_and_Export из класса DataMultiLoader в рамках отдельного изолированного потока.

private void LoadMultiThreading(eBookingProviders p_Provider, int p_nRRKey) {

DataMultiLoader amdLoader = new DataMultiLoader(amdLogger);

... Thread amdThread; ThreadStart thStart;

thStart = delegate () { amdLoader.Load_and_Export(p_Provider, nSelectedRRKey, oRScheduler); }; amdThread = new System.Threading.Thread(thStart); amdThread.Priority = ThreadPriority.AboveNormal; amdThread.IsBackground = true;
amdThread.Start(); ... }

Я просмотрел теоретические материалы по данной теме и выяснил, что в многопоточных приложениях существует проблема состязания за ресурсы, когда один поток портит данные другого.

Что надо добавить в существующий код, чтобы сделать его потоко безопасным?

Использование локальных блокировок данных с конструкциями типа

lock(lockObj) {
// синхронизируемые операторы
}

является неприемлемым из-за большого объёма существующего кода. По-хорошему здесь не нужна какая-либо синхронизация изменения данных, а нужна полная изолированность, как если бы процедура Load_and_Export запускалась в рамках отдельного процесса.

Можно ли достичь нужного результата если оформить класс DataMultiLoader следующим образом?

[Synchronization]
public class DataMultiLoader: ContextBoundObject
{
         ...
    public void Load_and_Export(eProviders p_Provider, int p_nRRKey, RoutesLoadScheduler p_RScheduler)
        {   
            ...
    }
}

Не будет ли в этом случае один поток, вызвавший Load_and_Export, ждать завершения этого же метода, вызванного ранее другим потоком из-за его полной блокировки ?

  • 1
    Вам проще будет запустить эту программу несколько раз с разными параметрами, т.е. воспользоваться процессами, вместо потоков. Как альтернатива - все глобальные переменные (в том числе статические) завернуть в ThreadLocal. – Roman-Stop RU aggression in UA Jun 15 '21 at 15:49
  • 1
    Чтобы один поток не портил данные другого, самый простой метод — чтобы не было разделяемых данных. Расскажите, что именно делает процедура, которая занимается загрузкой. В идеальном случае она должна получать все нужные для работы данные как параметры, не модифицировать глобальные данные. а возвращать результат в виде возвращаемого значения. – VladD Jun 15 '21 at 16:53
  • Вдогонку к @VladD: а допустимо ли будет класть полученные данные в буферы и ставить их в очередь, и дописать поток, который асинхронно смотрит за очередью и записывает все в БД? Тот еще велосипед, конечно... – Dimanson Jun 15 '21 at 20:20
  • 2
    @Dimanson: Этот велик официально называется producer-consumer. – VladD Jun 15 '21 at 21:03
  • 2Roman Konoval “Вам проще будет запустить эту программу несколько раз с разными параметрами, т.е. воспользоваться процессами, вместо потоков. “ Вы предлагаете сделать отдельный exe-модуль, в который вставить класс DataMultiLoader с процедурой загрузки и запускать его из Windows Service с параметрами, которые будут использоваться для вызова Load_and_Export ? Как крайний вариант такое наверно подойдёт. Только при остановке сервиса нужно будет также как-то удалять все, запущенные процессы, из памяти. – Сергей Jun 16 '21 at 09:01
  • 2Vlad Пока мне не очень понятно, что закладывается в понятие разделяемых данных. Из предоставленного примера кода видно, что в LoadMultiThreading (который вызывается периодически по таймеру) для каждого потока создаётся НОВЫЙ экземпляр класса DataMultiLoader и затем создаётся новый поток, в котором вызывается метод Load_and_Export этого класса. В данном случае будут разделяемые данные у данных потоков или у каждый поток будет работать со своими экземплярами полей ? Тут надо ещё учитывать, что из Load_and_Export вызываются методы другие классов, некоторые могут быть статическими. – Сергей Jun 16 '21 at 09:03
  • 2Dimanson Возможно это было бы приемлемо при построении нового приложения. Но здесь требуется доработать существующее. В таком случае потребуется его полностью переделать, что неприемлемо по трудозатратам. – Сергей Jun 16 '21 at 09:04
  • Благодарю всех откликнувшихся. Жаль, что не получил ответы на последние 2 вопроса из основного блока – Сергей Jun 16 '21 at 09:10
  • Вы предлагаете сделать отдельный exe-модуль - да. Что касается вопроса про то, как работает Synchronization, то его лучше задать отдельно. Это простой сфокусированный вопрос, и на него, уверен, здесь дадут ответ специалисты. Я оформил это в виде отдельного вопроса https://ru.stackoverflow.com/questions/1295814/%d0%9a%d0%b0%d0%ba-%d0%b2-net-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0%d0%b5%d1%82-%d0%b0%d1%82%d1%80%d0%b8%d0%b1%d1%83%d1%82-synchronization-%d0%b2-%d1%81%d0%b2%d1%8f%d0%b7%d0%ba%d0%b5-%d1%81-contextboundobject – Roman-Stop RU aggression in UA Jun 16 '21 at 09:39
  • 2Roman Konval Спасибо за рекомендации и создание дополнительной темы. Подождём, может ещё кто-нибудь что-то дельное скажет. Если Windows-сервис будет вызывать периодически один тот же внешний exe-модуль с различными параметрами. Могут ли в таком механизме существовать какие-либо ограничения ? – Сергей Jun 16 '21 at 12:50
  • Для процессов ограничения могут быть, если они использую разделяемые ресурсы вне процесса, например, пишут в тот же файл или изменяют те же строки в БД. – Roman-Stop RU aggression in UA Jun 16 '21 at 12:53
  • что закладывается в понятие разделяемых данных - это любые данные, к которым имеют доступ два (или больше) потока. Если говорить о экземплярах, которые созданы в функции и не сохраняются в глобальных (статических) переменных и не сохраняются в объектах, к которым имеют доступ разные потоки, то они не разделяемые, ведь к ним только один поток может получить доступ. – Roman-Stop RU aggression in UA Jun 16 '21 at 12:56
  • 2Roman Konoval Понял, спасибо. – Сергей Jun 17 '21 at 09:41

0 Answers0