Pergunta

Digamos que eu tenho este método simples:

public IEnumerable<uint> GetNumbers()
{
    uint n = 0;
    while(n < 100)
        yield return n++;
}

Como você tornaria esse tópico seguro? E com isso quero dizer que você receberia esse enumerador uma vez e que vários threads lidam com todos os números sem que ninguém seja duplicado.

Suponho que uma fechadura precisa ser usada em algum lugar, mas onde esse bloqueio deve ser para que um bloco de iterador seja seguro? O que, em geral, você precisa se lembrar se deseja um tópico seguro IEnumerable<T>? Ou melhor, acho que seria um tópico seguro IEnumerator<T>...?

Foi útil?

Solução

Há um problema inerente ao fazê -lo, porque IEnumerator<T> tem os dois MoveNext() e Current. Você realmente quer uma única chamada como:

bool TryMoveNext(out T value)

Nesse ponto você pode atomicamente Mova para o próximo elemento e obtenha um valor. Implementando isso e ainda sendo capaz de usar yield Pode ser complicado ... eu vou pensar sobre isso. Eu acho que você precisaria embrulhar o iterador "não-threadsafe" em um threads segura que executou atomicamente MoveNext() e Current Para implementar a interface mostrada acima. Eu não sei como você então envolveria essa interface de volta IEnumerator<T> para que você possa usá -lo em foreach no entanto...

Se você estiver usando .NET 4.0, extensões paralelas poderia Ser capaz de ajudá -lo - você precisaria explicar mais sobre o que está tentando fazer.

Este é um tópico interessante - posso ter que blogar sobre isso ...

Edit: eu agora blogou sobre isso com duas abordagens.

Outras dicas

Acabei de testar este bit de código:

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 () sempre chama a parte finalmente parte do código. Se você deseja que seu IEnumerable seja seguro, adicione quaisquer fechaduras que desejar em vez de writelines, murchar usando o Readerwriterslimlock, Semáforo, Monitor, etc.

Presumo que você precise de um enumerador de linha de threads, então provavelmente deve implementar esse.

Bem, não tenho certeza, mas talvez com algumas fechaduras no chamador?

Rascunho:

Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
  Monitor.Exit(syncRoot);
  //Do something with item
  Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);

Eu estava pensando que você não pode fazer o yield Palavra-chave segura, a menos que você dependa de uma fonte de valores já segura por threads:

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
}

Você precisará decidir se cada iniciação típica de um enumerador deve redefinir a sequência ou se o código do cliente deve fazer isso.

Você pode simplesmente retornar uma sequência completa a cada vez, em vez de usar o rendimento:

return Enumerable.Range(0, 100).Cast<uint>().ToArray();

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top