Я пишу простое приложение (для моей жены не менее :-P), которое выполняет некоторые манипуляции с изображениями (изменение размера, отметки времени и т. Д.) Для потенциально большой серии изображений. Поэтому я пишу библиотеку, которая может делать это как синхронно, так и асинхронно. Я решил использовать Асинхронный шаблон на основе событий . При использовании этого шаблона вам необходимо вызвать событие, когда работа будет завершена. Здесь я испытываю проблемы, зная, когда это будет сделано. Итак, в основном, в моем методе DownsizeAsync (асинхронный метод для уменьшения размера изображений) я делаю что-то вроде этого:
public void DownsizeAsync(string[] files, string destination)
{
foreach (var name in files)
{
string temp = name; //countering the closure issue
ThreadPool.QueueUserWorkItem(f =>
{
string newFileName = this.DownsizeImage(temp, destination);
this.OnImageResized(newFileName);
});
}
}
Самая сложная часть - знать, когда все они завершены.
Вот что я рассмотрел: Использование ManualResetEvents, как здесь: http://msdn.microsoft.com/en-us/library/3dasc8as%28VS.80%29.aspx Но проблема, с которой я столкнулся, состоит в том, что вы можете ждать только 64 или менее событий. У меня может быть намного больше изображений.
Второй вариант: иметь счетчик, который подсчитывает выполненные изображения, и поднять событие, когда счет достигнет общей суммы:
public void DownsizeAsync(string[] files, string destination)
{
foreach (var name in files)
{
string temp = name; //countering the closure issue
ThreadPool.QueueUserWorkItem(f =>
{
string newFileName = this.DownsizeImage(temp, destination);
this.OnImageResized(newFileName);
total++;
if (total == files.Length)
{
this.OnDownsizeCompleted(new AsyncCompletedEventArgs(null, false, null));
}
});
}
}
private volatile int total = 0;
Теперь это кажется "хакерским", и я не совсем уверен, что это потокобезопасно.
Итак, мой вопрос: каков наилучший способ сделать это? Есть ли другой способ синхронизации всех потоков? Я не должен использовать ThreadPool? Спасибо !!
ОБНОВЛЕНИЕ Основываясь на отзывах в комментариях и на нескольких ответах, я решил использовать этот подход:
Во-первых, я создал метод расширения, который объединяет перечислимое в «пакеты»:
public static IEnumerable<IEnumerable<T>> GetBatches<T>(this IEnumerable<T> source, int batchCount)
{
for (IEnumerable<T> s = source; s.Any(); s = s.Skip(batchCount))
{
yield return s.Take(batchCount);
}
}
В основном, если вы делаете что-то вроде этого:
foreach (IEnumerable<int> batch in Enumerable.Range(1, 95).GetBatches(10))
{
foreach (int i in batch)
{
Console.Write("{0} ", i);
}
Console.WriteLine();
}
Вы получите этот вывод:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95
Идея в том, что (как кто-то в комментариях указал) нет необходимости создавать отдельную ветку для каждого изображения. Поэтому я буду пакетировать изображения в число [machine.cores * 2] пакетов. Затем я воспользуюсь вторым подходом, который заключается в том, чтобы просто поддерживать счетчик, и когда счетчик достигнет ожидаемого результата, я буду знать, что все сделано.
Причина, по которой я теперь убежден, что это на самом деле потокобезопасность, заключается в том, что я пометил общую переменную как volatile, которое согласно MSDN :
Обычно используется летучий модификатор
для поля, к которому обращаются
несколько потоков без использования
оператор блокировки для сериализации доступа.
Использование модификатора volatile обеспечивает
что один поток извлекает больше всего
актуальная стоимость, написанная другим
нить
означает, что я должен быть в открытом состоянии (если нет, пожалуйста, дайте мне знать !!)
Итак, вот код, с которым я собираюсь:
public void DownsizeAsync(string[] files, string destination)
{
int cores = Environment.ProcessorCount * 2;
int batchAmount = files.Length / cores;
foreach (var batch in files.GetBatches(batchAmount))
{
var temp = batch.ToList(); //counter closure issue
ThreadPool.QueueUserWorkItem(b =>
{
foreach (var item in temp)
{
string newFileName = this.DownsizeImage(item, destination);
this.OnImageResized(newFileName);
total++;
if (total == files.Length)
{
this.OnDownsizeCompleted(new AsyncCompletedEventArgs(null, false, null));
}
}
});
}
}
Я открыт для обратной связи, так как я никоим образом не являюсь экспертом по многопоточности, поэтому, если кто-то видит какие-либо проблемы с этим или у него есть идея получше, пожалуйста, дайте мне знать. (Да, это просто домашнее приложение, но у меня есть некоторые идеи о том, как я могу использовать полученные знания для улучшения нашей службы поиска / индексации, которую мы используем на работе.) Пока я буду держать этот вопрос открытым до чувствую, что я использую правильный подход. Спасибо всем за помощь.