C #: Come posso creare un IEnumerable < T > thread sicuro?
-
05-07-2019 - |
Domanda
Dire che ho questo metodo semplice:
public IEnumerable<uint> GetNumbers()
{
uint n = 0;
while(n < 100)
yield return n++;
}
Come renderesti sicuro questo thread? E con questo intendo che otterresti quell'enumeratore una volta e che più thread gestiscano tutti i numeri senza che nessuno ottenga duplicati.
Suppongo che un blocco debba essere usato da qualche parte, ma dove deve essere quel blocco affinché un blocco iteratore sia sicuro per i thread? Cosa, in generale, devi ricordare se vuoi un thread sicuro IEnumerable < T >
? O meglio suppongo che sarebbe un thread IEnumerator < T >
sicuro per i thread??
Soluzione
C'è un problema intrinseco nel farlo, perché IEnumerator < T >
ha sia MoveNext ()
che Current
. Vuoi davvero una sola chiamata come:
bool TryMoveNext(out T value)
a quel punto puoi atomicamente passare all'elemento successivo e ottenere un valore. Implementarlo e riuscire a usare yield
potrebbe essere complicato ... Ci penserò comunque. Penso che dovresti avvolgere il "non thread-safe" iteratore in un thread-safe che ha eseguito atomicamente MoveNext ()
e Current
per implementare l'interfaccia mostrata sopra. Non so come riavvolgeresti questa interfaccia in IEnumerator < T >
in modo da poterlo usare in foreach
sebbene ...
Se stai usando .NET 4.0, Parallel Extensions potrebbe essere in grado di aiutarti - dovresti spiegare di più su cosa stai cercando di fare.
Questo è un argomento interessante - potrei dover blog su di esso ...
EDIT: ora ho blog su di esso con due approcci .
Altri suggerimenti
Ho appena testato questo bit di codice:
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 () chiama sempre la parte finalmente del codice. Se vuoi che il tuo IEnumerable sia thread-safe, aggiungi qualsiasi thread lock che desideri al posto delle scritte, appassisci usando ReaderWriterSlimLock, Semaphore, Monitor, ecc.
Presumo che tu abbia bisogno di un Enumeratore salva-thread, quindi dovresti probabilmente implementarlo.
Beh, non sono sicuro, ma forse con qualche lucchetto nel chiamante?
Progetto:
Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
Monitor.Exit(syncRoot);
//Do something with item
Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);
Stavo pensando che non puoi rendere la parola chiave yield
sicura per i thread, a meno che tu non faccia dipendere da una fonte di valori già sicura per i thread:
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
}
Dovrai decidere se ogni tipica iniziazione di un enumeratore deve reimpostare la sequenza o se il codice client deve farlo.
Potresti semplicemente restituire una sequenza completa ogni volta piuttosto che utilizzare la resa:
restituisce Enumerable.Range (0, 100) .Cast < uint > (). ToArray ();