以下代码是我尝试优化的代码的简化版本。
void Main()
{
var words = new List<string> {"abcd", "wxyz", "1234"};
foreach (var character in SplitItOut(words))
{
Console.WriteLine (character);
}
}
public IEnumerable<char> SplitItOut(IEnumerable<string> words)
{
foreach (string word in words)
{
var characters = GetCharacters(word);
foreach (char c in characters)
{
yield return c;
}
}
}
char[] GetCharacters(string word)
{
Thread.Sleep(5000);
return word.ToCharArray();
}
我无法更改方法SplitItOut的签名.GetCharacters方法的调用成本很高,但是是线程安全的。 SplitItOut方法的输入可以包含100,000多个条目,并且对GetCharacters()方法的单次调用可能需要200毫秒左右。它还会引发我可以忽略的异常。结果的顺序无关紧要。
在我的第一次尝试中,我想出了以下使用TPL的实现,该实现可以大大加快速度,但是一直阻塞直到我完成所有单词的处理。
public IEnumerable<char> SplitItOut(IEnumerable<string> words)
{
Task<char[][]> tasks = Task<char[][]>.Factory.StartNew(() =>
{
ConcurrentBag<char[]> taskResults = new ConcurrentBag<char[]>();
Parallel.ForEach(words,
word =>
{
taskResults.Add(GetCharacters(word));
});
return taskResults.ToArray();
});
foreach (var wordResult in tasks.Result)
{
foreach (var c in wordResult)
{
yield return c;
}
}
}
我正在寻找比这更好的方法SplitItOut()的实现。缩短处理时间是我的首要任务。
最佳答案
如果我正确地阅读了您的问题,那么您不是要加快从单词创建字符的并行处理的速度-您希望您的枚举数能够在准备好每个单词后立即产生。使用您当前拥有的实现(以及我目前看到的其他答案),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
方法更改为要做四件事: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);
}
}
}
}
这只是