有没有办法对 C# 中的排列枚举进行
foreach
样式迭代?对于可下标列表,我知道可以使用常规的 for
循环在索引范围内迭代 int,但出于多种原因,我真的更喜欢 foreach
而不是 for
。
如果它能在 C# 2.0 中运行,还有加分。
澄清:我说的是多重排列的枚举。就像一排排的铅笔和纸。我希望每支铅笔都能在相应的纸上写字。
注意: 最初这个问题使用术语 parallel 而不是 lined up。
.NET 4 的 BlockingCollection 使这变得非常简单。创建一个BlockingCollection,在可枚举方法中返回其.GetConsumingEnumerable()。然后 foreach 只是添加到阻塞集合中。
例如
private BlockingCollection<T> m_data = new BlockingCollection<T>();
public IEnumerable<T> GetData( IEnumerable<IEnumerable<T>> sources )
{
Task.Factory.StartNew( () => ParallelGetData( sources ) );
return m_data.GetConsumingEnumerable();
}
private void ParallelGetData( IEnumerable<IEnumerable<T>> sources )
{
foreach( var source in sources )
{
foreach( var item in source )
{
m_data.Add( item );
};
}
//Adding complete, the enumeration can stop now
m_data.CompleteAdding();
}
希望这有帮助。 顺便说一句,我昨晚发布了关于此的博客
安德烈
foreach
一次仅适用于一个可枚举。但是,如果将并行可枚举性合并为一个,则可以
foreach
合并后的可枚举性。我不知道有任何简单的内置方法可以做到这一点,但以下应该可以工作(尽管我还没有测试过):
public IEnumerable<TSource[]> Combine<TSource>(params object[] sources)
{
foreach(var o in sources)
{
// Choose your own exception
if(!(o is IEnumerable<TSource>)) throw new Exception();
}
var enums =
sources.Select(s => ((IEnumerable<TSource>)s).GetEnumerator())
.ToArray();
while(enums.All(e => e.MoveNext()))
{
yield return enums.Select(e => e.Current).ToArray();
}
}
然后你可以
foreach
返回的枚举:
foreach(var v in Combine(en1, en2, en3))
{
// Remembering that v is an array of the type contained in en1,
// en2 and en3.
}
“如何同时迭代两个数组”的答案。
C# 3.5 中的并行 ForEach 循环 用途:
string[] names = { "cartman", "stan", "kenny", "kyle" };
names.EachParallel(name =>
{
try
{
Console.WriteLine(name);
}
catch { /* handle exception */ }
});
实施:
/// <summary>
/// Enumerates through each item in a list in parallel
/// </summary>
public static void EachParallel<T>(this IEnumerable<T> list, Action<T> action)
{
// enumerate the list so it can't change during execution
list = list.ToArray();
var count = list.Count();
if (count == 0)
{
return;
}
else if (count == 1)
{
// if there's only one element, just execute it
action(list.First());
}
else
{
// Launch each method in it's own thread
const int MaxHandles = 64;
for (var offset = 0; offset < list.Count() / MaxHandles; offset++)
{
// break up the list into 64-item chunks because of a limitiation // in WaitHandle
var chunk = list.Skip(offset * MaxHandles).Take(MaxHandles);
// Initialize the reset events to keep track of completed threads
var resetEvents = new ManualResetEvent[chunk.Count()];
// spawn a thread for each item in the chunk
int i = 0;
foreach (var item in chunk)
{
resetEvents[i] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback((object data) =>
{
int methodIndex = (int)((object[])data)[0];
// Execute the method and pass in the enumerated item
action((T)((object[])data)[1]);
// Tell the calling thread that we're done
resetEvents[methodIndex].Set();
}), new object[] { i, item });
i++;
}
// Wait for all threads to execute
WaitHandle.WaitAll(resetEvents);
}
}
}
public static IEnumerable<TSource[]> Combine<TSource> (this IEnumerable<IEnumerable<TSource>> sources)
{
var enums = sources
.Select (s => s.GetEnumerator ())
.ToArray ();
while (enums.All (e => e.MoveNext ())) {
yield return enums.Select (e => e.Current).ToArray ();
}
}
public static IEnumerable<TSource[]> Combine<TSource> (params IEnumerable<TSource>[] sources)
{
return sources.Combine ();
}
public static class Parallel
{
public static void ForEach<T>(IEnumerable<T>[] sources,
Action<T> action)
{
foreach (var enumerable in sources)
{
ThreadPool.QueueUserWorkItem(source => {
foreach (var item in (IEnumerable<T>)source)
action(item);
}, enumerable);
}
}
}
// sample usage:
static void Main()
{
string[] s1 = { "1", "2", "3" };
string[] s2 = { "4", "5", "6" };
IEnumerable<string>[] sources = { s1, s2 };
Parallel.ForEach(sources, s => Console.WriteLine(s));
Thread.Sleep(0); // allow background threads to work
}
对于 C# 2.0,您需要将上面的 lambda 表达式转换为委托。
注意:此实用方法使用后台线程。您可能想要修改它以使用前台线程,并且可能需要等到所有线程完成。如果您这样做,我建议您创建
sources.Length - 1
线程,并将当前执行线程用于最后一个(或第一个)源。(我希望我可以在代码中包含等待线程完成,但很抱歉我还不知道该怎么做。我想你应该使用
a WaitHandle
Thread.Join()
。)