views:

57

answers:

0

Just for the heck of it I'm trying to emulate how JRuby generators work using threads in C#.

Also, I'm fully aware that C# has built in support for yield return, I'm just toying around a bit.

I guess it's some sort of poor mans coroutines by keeping multiple callstacks alive using threads. (even though none of the callstacks should execute at the same time)

The idea is like this:

  • The consumer thread requests a value
  • The worker thread provides a value and yields back to the consumer thread
  • Repeat untill worker thread is done

So, what would be the correct way of doing the following?

//example
class Program
{
    static void Main(string[] args)
    {
        ThreadedEnumerator<string> enumerator = new ThreadedEnumerator<string>();

        enumerator.Init(() =>
            {
                for (int i = 1; i < 100; i++)
                {
                    enumerator.Yield(i.ToString());
                }
            });

        foreach (var item in enumerator)
        {
            Console.WriteLine(item);
        };

        Console.ReadLine();
    }
}

//naive threaded enumerator
public class ThreadedEnumerator<T> : IEnumerator<T>, IEnumerable<T>
{
    private Thread enumeratorThread;
    private T current;
    private bool hasMore = true;
    private bool isStarted = false;
    AutoResetEvent enumeratorEvent = new AutoResetEvent(false);
    AutoResetEvent consumerEvent = new AutoResetEvent(false);
    public void Yield(T item)
    {
        //wait for consumer to request a value
        consumerEvent.WaitOne();

        //assign the value
        current = item;

        //signal that we have yielded the requested
        enumeratorEvent.Set();
    }

    public void Init(Action userAction)
    {
        Action WrappedAction = () =>
        {
            userAction();
            consumerEvent.WaitOne();
            enumeratorEvent.Set();
            hasMore = false;
        };
        ThreadStart ts = new ThreadStart(WrappedAction);
        enumeratorThread = new Thread(ts);
        enumeratorThread.IsBackground = true;
        isStarted = false;
    }

    public T Current
    {
        get { return current; }
    }

    public void Dispose()
    {
        enumeratorThread.Abort();
    }

    object System.Collections.IEnumerator.Current
    {
        get { return Current; }
    }

    public bool MoveNext()
    {
        if (!isStarted)
        {
            isStarted = true;
            enumeratorThread.Start();
        }
        //signal that we are ready to receive a value
        consumerEvent.Set();

        //wait for the enumerator to yield
        enumeratorEvent.WaitOne();

        return hasMore;
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this;
    }
}

Ideas?