Ответ 1
Да, метод должен иметь возможность работать в нескольких потоках. Единственное, о чем вам следует беспокоиться, - это одновременный доступ к одному файлу в нескольких потоках.
Я читал Threading внутри класса со статическими и нестационарными методами, и я вхожу в подобную ситуацию.
У меня есть статический метод, который извлекает данные из ресурса и создает некоторые объекты времени выполнения на основе данных.
static class Worker{
public static MyObject DoWork(string filename){
MyObject mo = new MyObject();
// ... does some work
return mo;
}
}
Метод занимает некоторое время (в этом случае он считывает файлы 5-10 Мбайт) и возвращает объект.
Я хочу использовать этот метод и использовать его в ситуации с несколькими потоками, чтобы сразу прочитать несколько файлов. Проблемы с дизайном/рекомендации в сторону, как несколько потоков будут обращаться к этому коду?
Скажем, у меня есть что-то вроде этого...
class ThreadedWorker {
public void Run() {
Thread t = new Thread(OnRun);
t.Start();
}
void OnRun() {
MyObject mo = Worker.DoWork("somefilename");
mo.WriteToConsole();
}
}
Выполняется ли статический метод для каждого потока, что позволяет выполнять параллельное выполнение?
Да, метод должен иметь возможность работать в нескольких потоках. Единственное, о чем вам следует беспокоиться, - это одновременный доступ к одному файлу в нескольких потоках.
В этом случае следует различать статические методы и статические поля. Каждый вызов статического метода будет иметь свою собственную "копию" метода и его локальных переменных. Это означает, что в вашем примере каждый вызов будет работать на свой собственный экземпляр MyObject
, и вызовы не будут иметь ничего общего друг с другом. Это также означает, что нет проблем с выполнением их в разных потоках.
Если статический метод написан как потокобезопасный, он может быть вызван из любого потока или даже передан пулу потоков.
Вы должны иметь в виду - объекты .NET не живут в потоках (за исключением структур, расположенных в стеке потоков) - пути выполнения. Таким образом, если поток может получить доступ к экземпляру объекта, он может вызвать метод экземпляра. Любой поток может вызывать статический метод, потому что все это нужно знать о типе объекта.
Одна вещь, которую вы должны иметь в виду при одновременном выполнении статических методов, - это статические поля, которые существуют только один раз. Таким образом, если метод считывает и записывает статические поля, могут возникать проблемы совпадения.
Однако есть атрибут ThreadStaticAttribute
, который говорит, что для каждого потока есть отдельное поле. Это может быть полезно в некоторых конкретных сценариях.
Локальные переменные являются separte для каждого потока, поэтому вам не нужно заботиться об этом. Но имейте в виду внешние ресурсы, такие как файлы, которые могут быть проблематичными при одновременном доступе.
С наилучшими пожеланиями,
Оливер Ханаппи
Помимо аспекта кода, на который уже был дан ответ, вам также необходимо рассмотреть аспект ввода-вывода для доступа к файлу.
Заметка об архитектуре и о том, как я завершил эту задачу в прошлом, - не предполагая, что это один правильный подход или что это обязательно подходит для вашего приложения. Тем не менее, я думал, что мои заметки могут быть полезны для вашего мыслительного процесса:
Настройте поле ManualResetEvent, назовите его ActivateReader или что-то подобное, это станет более очевидным. Инициализируйте его как false.
Настройте логическое поле, назовите его TerminateReaderThread. Инициализируйте его как false, снова это станет более очевидным.
Настроить очередь и lt; string > поле, вызовите его Файлы и инициализируйте его.
Мой основной поток приложений проверяет, есть ли блокировка в очереди файлов перед записью каждого из соответствующих путей к файлу. После того, как файл был записан, событие reset отключено, указывая на поток читателя очереди, что в очереди есть непрочитанные файлы.
Затем я настроил поток, чтобы действовать как читатель очереди. Этот поток ожидает, что ManualResetEvent сработает с использованием метода WaitAny() - это блокирующий метод, который разблокируется после счистки ManualResetEvent. После того, как он сработал, поток проверяет, было ли инициировано завершение потока [путем проверки поля TerminateReaderThread]. Если инициализация завершена, поток отключается изящно, иначе он считывает следующий элемент из очереди и порождает рабочий поток для обработки файла. Затем я блокирую очередь перед проверкой, не осталось ли предметов. Если элементы не оставлены, я reset ManualResetEvent, который остановит наш поток в следующем раунде. Затем я разблокирую очередь, чтобы основной поток мог продолжить писать.
Каждый экземпляр рабочего потока пытается получить эксклюзивную блокировку файла, с которым он был инициирован, до тех пор, пока не истечет некоторый тайм-аут, если блокировка прошла успешно, он обрабатывает файл, если он не увенчался успехом, он либо повторяет по мере необходимости, исключение и завершает себя. В случае исключения поток может добавить файл в конец очереди, чтобы другой поток мог снова забрать его в более поздней точке. Имейте в виду, что если вы это сделаете, вам нужно рассмотреть бесконечный цикл, который может вызвать проблема чтения ввода-вывода. В таком случае словарь неудачных файлов со счетчиками, сколько раз они были неудачными, может быть полезным, так что, если бы достигнут какой-то предел, вы могли бы перестать повторно добавлять файл в конец очереди.
Как только мое приложение решит, что поток читателя больше не нужен, он устанавливает для поля TerminateReaderThread значение true. В следующий раз, когда поток читателя начнет цикл до начала его процесса, его процесс останова будет активирован.
Статический метод будет запускаться в потоке, из которого вы его вызываете. До тех пор, пока ваша функция повторится, то есть выполнение может безопасно повторно войти в функцию, пока выполнение из другого потока (или далее вверх по стеку) уже находится в функции.
Поскольку ваша функция статична, вы не можете получить доступ к переменным-членам, что было бы одним из способов сделать его не повторным. Если бы у вас была статическая локальная переменная, которая поддерживала состояние, это было бы другим способом сделать ее не повторной.
Каждый раз, когда вы входите, вы создаете новый объект MyObject, поэтому каждый бит потока выполнения имеет дело с собственным экземпляром MyObject, что хорошо. Это означает, что они не будут пытаться получить доступ к одному и тому же объекту одновременно (что приведет к условиям гонки).
Единственное, что вы используете между несколькими вызовами, это сама консоль. Если вы вызываете его по нескольким потокам, они будут выводиться друг на друга в консоли. И вы можете потенциально действовать в одном файле (в вашем примере имя файла жестко запрограммировано), но вы, вероятно, будете воздействовать на несколько файлов. Последовательные потоки, вероятно, не смогут открыть файл, если предыдущие его открыли.