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??

È stato utile?

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 ();

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top