C#:IEnumerable< T>を作成するにはどうすればよいですか?スレッドセーフ?
-
05-07-2019 - |
質問
この簡単な方法があるとします:
public IEnumerable<uint> GetNumbers()
{
uint n = 0;
while(n < 100)
yield return n++;
}
このスレッドをどのように安全にしますか?それにより、その列挙子を1回取得し、複数のスレッドが重複を取得することなくすべての数値を処理することを意味します。
どこかでロックを使用する必要があると思いますが、イテレータブロックがスレッドセーフであるためには、そのロックはどこにある必要がありますか?一般的に、スレッドセーフな IEnumerable&lt; T&gt;
が必要な場合、何を覚えておく必要がありますか?それとも、スレッドセーフな IEnumerator&lt; T&gt;
...?
解決
IEnumerator&lt; T&gt;
には MoveNext()
と Current
の両方があるため、これには固有の問題があります。次のような単一の呼び出しが本当に必要です。
bool TryMoveNext(out T value)
その時点で、次の要素に原子的に移動して値を取得できます。それを実装し、それでも yield
を使用できるようにするのは難しいかもしれません...考えてみます。 「非スレッドセーフ」をラップする必要があると思います。上記のインターフェイスを実装するために MoveNext()
および Current
をアトミックに実行したスレッドセーフなイテレータ。このインターフェイスを IEnumerator&lt; T&gt;
にラップして、 foreach
で使用できるようにする方法がわからない...
.NET 4.0を使用している場合、Parallel Extensionsが役立つ場合があります。
これは興味深いトピックです-ブログを書く必要があるかもしれません...
編集:2つのアプローチでブログを作成しました。
他のヒント
次のコードをテストしました:
static IEnumerable<int> getNums()
{
Console.WriteLine("IENUM - ENTER");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
yield return i;
}
Console.WriteLine("IENUM - EXIT");
}
static IEnumerable<int> getNums2()
{
try
{
Console.WriteLine("IENUM - ENTER");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
yield return i;
}
}
finally
{
Console.WriteLine("IENUM - EXIT");
}
}
getNums2()は、常にコードの最終部分を呼び出します。 IEnumerableをスレッドセーフにしたい場合は、WriteWriterの代わりに、ReaderWriterSlimLock、Semaphore、Monitorなどを使用して、必要なスレッドロックを追加します。
スレッド保存の列挙子が必要だと思いますので、おそらくそれを実装する必要があります。
まあ、よくわかりませんが、呼び出し側にロックがかかっているのでしょうか?
下書き:
Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
Monitor.Exit(syncRoot);
//Do something with item
Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);
すでにスレッドセーフな値のソースに依存する場合を除き、 yield
キーワードをスレッドセーフにすることはできないと考えていました。
public interface IThreadSafeEnumerator<T>
{
void Reset();
bool TryMoveNext(out T value);
}
public class ThreadSafeUIntEnumerator : IThreadSafeEnumerator<uint>, IEnumerable<uint>
{
readonly object sync = new object();
uint n;
#region IThreadSafeEnumerator<uint> Members
public void Reset()
{
lock (sync)
{
n = 0;
}
}
public bool TryMoveNext(out uint value)
{
bool success = false;
lock (sync)
{
if (n < 100)
{
value = n++;
success = true;
}
else
{
value = uint.MaxValue;
}
}
return success;
}
#endregion
#region IEnumerable<uint> Members
public IEnumerator<uint> GetEnumerator()
{
//Reset(); // depends on what behaviour you want
uint value;
while (TryMoveNext(out value))
{
yield return value;
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
//Reset(); // depends on what behaviour you want
uint value;
while (TryMoveNext(out value))
{
yield return value;
}
}
#endregion
}
列挙子の典型的な開始ごとにシーケンスをリセットするか、クライアントコードでリセットする必要があるかを決定する必要があります。
yieldを使用するのではなく、毎回完全なシーケンスを返すことができます:
return Enumerable.Range(0、100).Cast&lt; uint&gt;()。ToArray();