Есть задача. Учебная, так что вопросы оптимальности решения отходят на второй план.
Есть 3 потока, работу которых нужно синхронизировать.
Первый поток собирает информацию о файлах и папках. Работает циклично и рекурсивно.
Найдя информацию, он должен остановиться и дать потокам 2 и 3 записать ее в файл и память. Когда потоки 2 и 3 закончат запись, поток 1 возобновляет работу и ищет следующий файл(папку).
Потоков должно быть 3. Писать каждый найденный файл в новом потоке - не вариант.
Вопросы следующие:
1. Какой механизм лучше применить для синхронизации потоков: AutoResetEvent (свой для каждого из двух потоков - см. код), ManualResetEvent или Semaphore.
2. Как нужно исправить код, чтобы потоки запускались и останавливались когда нужно? Буду благодарен за пояснения почему именно так, потому что с методами Set(), Reset(), Wait() я игрался долго, но безуспешно.
3. Как правильно организовать остановку вторичных потоков после окончания работы первичного? Я приравнял null первичный поток, но думаю, что это не лучшая идея.
4. И был бы благодарен за разъяснение как решать аналогичные задачи в нормальных, не учебных проектах.
5. Нужно ли в данном примере проводить считывание информации вторичными потоками в критической секции? Как я предполагаю, информация в процессе считывания не поменяется, так как основной поток ждет завершения вторичных, и необходимости в критической секции нет.
Код:
private Queue<Nested3> collection;
static int counter=100;
object locked;
Thread SearcherThread;
Thread Handler1Thread;
Thread Handler2Thread;
static WaitHandle[] events;
private class Nested3
{
public int Val;
public bool IsHandled;
public Nested3(int val)
{
this.Val = val;
IsHandled = false;
}
}
public ARETest()
{
collection = new Queue<Nested3>();
events = new WaitHandle[] { new AutoResetEvent(false), new AutoResetEvent(false) };
SearcherThread = new Thread(Searcher);
SearcherThread.Start();
Handler1Thread = new Thread(Handler1);
Handler2Thread = new Thread(Handler2);
locked = new object();
}
private void Searcher()
{
// Очередь для двух задач в двух разных потоках.
ThreadPool.QueueUserWorkItem(new WaitCallback(Handler1), events[0]);
ThreadPool.QueueUserWorkItem(new WaitCallback(Handler2), events[1]);
for (int i = 0; i < 3; i++)
{
Thread.Sleep(2); // имитация поиска
Monitor.Enter(locked); // применение класса Monitor для создания критической секции обусловлено условием задачи
collection.Enqueue(new Nested3(++counter)); // Найден первый пакет информации для записи вторичными потками
Monitor.Exit(locked);
(events[0] as AutoResetEvent).Set(); // запуск вторичных потоков
(events[1] as AutoResetEvent).Set();
(events[0] as AutoResetEvent).Reset(); // сброс в несигнальное состояние
(events[1] as AutoResetEvent).Reset();
// Ожидание пока все задачи завершаться.
WaitHandle.WaitAll(events);
Monitor.Enter(locked); // Очистка очереди от записанной информации
collection.Dequeue();
Monitor.Exit(locked);
if (i<2) Searcher(); // Рекурсивный вызов
}
SearcherThread = null;
// Запуск последнего прохода цикла вторичными потоками
(events[0] as AutoResetEvent).Set();
(events[1] as AutoResetEvent).Set();
}
private void Handler1(object state)
{
var auto = (AutoResetEvent)state;
do
{
auto.WaitOne(); // здесь я хочу чтобы потоки ждали сигнала от первичного потока
Thread.Sleep(15); // имитация записи в файл
auto.Set(); // Здесь вторичный поток должен дать первичному сигнал, что он закончил итерацию
} while (SearcherThread != null);
}
private void Handler2(Object state) // работает аналогично предыдущему методу, но с другой задержкой
{
var auto = (AutoResetEvent)state;
do
{
auto.WaitOne(); // здесь я хочу чтобы потоки ждали сигнала от первичного потока
Thread.Sleep(15); // имитация записи в файл
auto.Set(); // Здесь вторичный поток должен дать первичному сигнал, что он закончил итерацию
} while (SearcherThread != null);
}
}
А нужна ли Вам здесь вообще прямая работа с потоками? Для выполнения нескольких задач параллельно можно использовать высокоуровневый приметив Parallel.Invoke
.
static int counter=100;
private class Nested3 {
public int Val;
public bool IsHandled;
public Nested3(int val) {
this.Val = val;
IsHandled = false;
}
}
private void Searcher() {
for(int i = 0; i < 3; i++) {
Thread.Sleep(2); // имитация поиска
Nested3 currentItem = new Nested3(++counter);
Parallel.Invoke(() => Handler1(currentItem), () => Handler2(currentItem));
}
}
private void Handler1(Nested3 currentItem) {
Thread.Sleep(15); // имитация записи в файл
}
private void Handler2(Nested3 currentItem) {
Thread.Sleep(15); // имитация записи в файл
}
Если важно использовать именно WaitHandle
, то можно поступить следующим образом:
private AutoResetEvent done = new AutoResetEvent(false);
private void Searcher() {
for(int i = 0; i < 3; i++) {
Thread.Sleep(2); // имитация поиска
Nested3 currentItem = new Nested3(++counter);
ThreadPool.QueueUserWorkItem(_ => {
Handler2(currentItem);
done.Set();
}); // одно из заданий выполняем в пуле потоков
Handler1(currentItem); // а другое выполняем в текущем потоке
done.WaitOne(); // ждём завершения задания, которое отдали пулу потоков
}
}
Спасибо за ответ. Такое решение мне в голову не пришло.
В общем, в итоге метод Searcher() выглядит так.
private void Searcher()
{
for (int i = 0; i < 3; i++)
{
Thread.Sleep(2);
Monitor.Enter(locked);
collection.Enqueue(new Nested3(++counter)); // теперь очередь можно заменить на единичный объект класса
Monitor.Exit(locked);
ThreadPool.QueueUserWorkItem(_ =>
{
Handler2();
(events[1] as AutoResetEvent).Set();
});
ThreadPool.QueueUserWorkItem(_ =>
{
Handler1();
(events[0] as AutoResetEvent).Set();
});
WaitHandle.WaitAll(events);
Console.WriteLine("Доджался");
Monitor.Enter(locked);
collection.Dequeue();
Monitor.Exit(locked);
Console.WriteLine("Поиск отработан");
Console.WriteLine();
if (counter<105) Searcher(); // рекурсивыный вызов
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Здравствуйте! Хочу защитить программу на C# (WPF,NET 4
Использую контекстное меню для изменения строк DataGridКаждый пункт меню представляет собой цвет, который применяется к строкам таблицы
Как сравнить элементы коллекции каждый с каждым? Немного обьясню, у меня есть метод, который работает с двумя обьектами, вызываю его так objectnamemethod(objectname1)в...
Как в Unity сделать эмуляцию нажатия клавиши на клавиатуре? Чтобы на него реагировали и внешние программыМышкой мы можем, например, подвигать...