Если я правильно читаю ваш вопрос, вы не хотите просто ускорить параллельную обработку, которая создает символы из слов, - вы хотели бы, чтобы ваш перечисляемый производил каждый , как только он будет готов .С реализацией, которая у вас есть (и другими ответами, которые я сейчас вижу), SplitItOut
будет ждать, пока все слова не будут отправлены на GetCharacters
, и все результаты вернутся, прежде чем произойдет первое.
В подобных случаях мне нравится думать о вещах как о том, что я делю процесс на производителей и потребителей.Ваш продюсер поток (ов) возьмет доступные слова и вызовет GetCharacters, а затем выдаст результаты куда-нибудь.Потребитель выдаст символы для вызывающего SplitItOut
, как только они будут готовы.На самом деле, потребитель является вызывающим абонентом SplitItOut
.
. Мы можем использовать BlockingCollection
как для получения символов, так и в качестве «где-то» дляпоставить результаты.Мы можем использовать ConcurrentBag
в качестве места для размещения слов, которые еще предстоит разделить:
static void Main()
{
var words = new List<string> { "abcd", "wxyz", "1234"};
foreach (var character in SplitItOut(words))
{
Console.WriteLine(character);
}
}
static char[] GetCharacters(string word)
{
Thread.Sleep(5000);
return word.ToCharArray();
}
Без изменений в main
или GetCharacters
- так как этипредставлять ваши ограничения (не может изменить вызывающего абонента, не может изменить дорогостоящую операцию)
public static IEnumerable<char> SplitItOut(IEnumerable<string> words)
{
var source = new ConcurrentBag<string>(words);
var chars = new BlockingCollection<char>();
var tasks = new[]
{
Task.Factory.StartNew(() => CharProducer(source, chars)),
Task.Factory.StartNew(() => CharProducer(source, chars)),
//add more, tweak away, or use a factory to create tasks.
//measure before you simply add more!
};
Task.Factory.ContinueWhenAll(tasks, t => chars.CompleteAdding());
return chars.GetConsumingEnumerable();
}
Здесь мы изменим метод SplitItOut
, чтобы сделать четыре вещи:
- Initializeпараллельная сумка со всеми словами, которые мы хотим разделить.(примечание: если вы хотите перечислять слова по требованию, вы можете запустить новое задание, чтобы вставить их вместо того, чтобы делать это в конструкторе)
- Запустить наши задачи типа «производитель».Вы можете начать набор номера, использовать фабрику, что угодно.Я предлагаю не сходить с ума от заданий до того, как вы измерите.
- Сигнализирует
BlockingCollection
, что мы закончили, когда все задачи завершены. - «Поглотили» все произведенные символы (мы делаемлегко для себя и просто верните
IEnumerable<char>
вместо foreach и yield, но вы можете сделать это долго, если хотите)
Все, чего не хватает, - это реализации нашего производителя.Я расширил все ярлыки linq, чтобы прояснить его, но он очень прост:
private static void CharProducer(ConcurrentBag<string> words, BlockingCollection<char> output)
{
while(!words.IsEmpty)
{
string word;
if(words.TryTake(out word))
{
foreach (var c in GetCharacters(word))
{
output.Add(c);
}
}
}
}
Это просто
- Вынимает слово из ConcurrentBag (если оно не пусто).- если это так, задача выполнена!)
- Вызывает дорогой метод
- Помещает вывод в BlockingCollection