Я пишу простое приложение (для моей жены не менее: -P), которое делает некоторые манипуляции с изображениями (изменение размера, временная привязка и т.д.) для потенциально большой партии изображений. Поэтому я пишу библиотеку, которая может делать это как синхронно, так и асинхронно. Я решил использовать Асинхронный шаблон на основе событий. При использовании этого шаблона вам нужно поднять событие, когда работа будет завершена. Здесь я испытываю проблемы, зная, когда это будет сделано. Итак, в моем методе DownsizeAsync (метод async для сокращения изображений) я делаю что-то вроде этого:
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] количество партий. Затем я воспользуюсь своим вторым подходом, который просто будет поддерживать счетчик, и когда счетчик достигнет общей суммы, которую я ожидаю, я узнаю, что я закончил.
Причина, по которой я сейчас убежден, что она действительно потокобезопасна, заключается в том, что я обозначил полную переменную как изменчивую, которая согласно MSDN:
Обычно используется изменчивый модификатор для поля, к которому осуществляется доступ несколько потоков без использования блокировки для сериализации доступа. Использование изменчивого модификатора обеспечивает что один поток извлекает больше всего обновленное значение, написанное другим нить
означает, что я должен быть в ясном (если нет, пожалуйста, дайте мне знать!)
Итак, вот код, который я собираюсь:
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));
}
}
});
}
}
Я открыт для обратной связи, поскольку я никоим образом не являюсь экспертом по многопоточности, поэтому, если кто-либо видит какую-либо проблему с этим или имеет лучшую идею, пожалуйста, дайте мне знать. (Да, это только домашнее приложение, но у меня есть некоторые идеи о том, как я могу использовать полученные знания, чтобы улучшить наш сервис поиска/индекса, который мы используем на работе.) Пока я буду держать этот вопрос открытым до тех пор, пока я чувствую, что я использую правильный подход. Спасибо всем за вашу помощь.