C #: Wie kann ich eine IEnumerable Thread-sicher?
-
05-07-2019 - |
Frage
Sagen, ich habe diese einfache Methode:
public IEnumerable<uint> GetNumbers()
{
uint n = 0;
while(n < 100)
yield return n++;
}
Wie werden Sie dieses Thema sicher machen? Und damit meine ich, dass Sie das Enumerator einmal bekommen würde, und haben mehrere Threads verarbeiten alle Zahlen, ohne dass jemand immer Duplikate.
Ich nehme eine Sperre verwendet irgendwo werden muss, aber wo muss das Schloss sein für einen Iteratorblock wird Thread-sicher? Was im Allgemeinen, müssen Sie sich merken, wenn Sie einen Thread-sicher IEnumerable<T>
wollen? Oder besser gesagt ich denke, es wäre ein Thread-sicher IEnumerator<T>
sein ...?
Lösung
Es ist ein inhärentes Problem dabei, weil IEnumerator<T>
sowohl MoveNext()
und Current
hat. Sie wollen wirklich einen einzigen Anruf wie:
bool TryMoveNext(out T value)
an diesem Punkt kann man atomar Umzug in das nächste Element und einen Wert erhalten. Die Implementierung dass und noch in der Lage zu sein yield
verwenden könnte schwierig sein ... Ich werde zwar eine Meinung darüber. Ich glaube, Sie müssen das „Nicht-THREAD“ Iterator in einem THREAD eines einzuwickeln, die atomar MoveNext()
und Current
durchgeführt, um die Schnittstelle zu implementieren oben gezeigt. Ich weiß nicht, wie Sie dann diese Schnittstelle wieder in IEnumerator<T>
wickeln würden, so dass Sie es in foreach
verwenden könnten aber ...
Wenn Sie .NET 4.0, Parallel Extensions können der Lage sein, mit Ihnen zu helfen, -. Sie bräuchten mehr darüber zu erklären, was Sie versuchen, obwohl zu tun
Dies ist ein interessantes Thema - ich kann über sie haben zum Blog ...
EDIT: Ich habe jetzt darüber gebloggt mit zwei Ansätzen .
Andere Tipps
Ich habe dieses Stück Code getestet:
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 () ruft immer die schließlich Teil des Codes. Wenn Sie Ihr IEnumerable Faden sicher zu sein, hinzufügen, was Thread Sperren Sie statt writelines wollen, verdorren mit ReaderWriterSlimLock, Semaphore, Monitor, etc.
Ich gehe davon aus, dass Sie eine Thread-sichere Enumeration müssen, so sollten Sie vielleicht, dass man implementieren.
Nun, ich bin nicht sicher, aber vielleicht mit einigen Schlössern in den Anrufern?
Entwurf:
Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
Monitor.Exit(syncRoot);
//Do something with item
Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);
Ich dachte, dass Sie nicht das yield
Schlüsselwort Thread-sicher machen, wenn Sie es auf eine bereits Thread-sichere Quelle von Werten abhängig machen:
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
}
Sie werden sich entscheiden müssen, ob jede typische Einleitung eines Aufzählungs sollte die Sequenz zurückgesetzt, oder wenn der Client-Code das tun muss.
Sie könnten nur eine vollständige Sequenz zurückkehren jedes Mal, anstatt Verwendung Ausbeute:
return Enumerable.Range(0, 100).Cast<uint>().ToArray();