How can I duplicate the F# discriminated union type in C#?

后端 未结 6 957
抹茶落季
抹茶落季 2020-12-09 18:10

I\'ve created a new class called Actor which processes messages passed to it. The problem I am running into is figuring out what is the most elegant way to pass related but

6条回答
  •  被撕碎了的回忆
    2020-12-09 18:51

    In your example code, you implement PostWithAsyncReply in terms of PostWithReply. That isn't ideal, because it means that when you call PostWithAsyncReply and the actor takes a while to handle it, there are actually two threads tied up: the one executing the actor and the one waiting for it to finish. It would be better to have the one thread executing the actor and then calling the callback in the asynchronous case. (Obviously in the synchronous case there's no avoiding the tying up of two threads).

    Update:

    More on the above: you construct an actor with an argument telling it how many threads to run. For simplicity suppose every actor runs with one thread (actually quite a good situation because actors can then have internal state with no locking on it, as only one thread accesses it directly).

    Actor A calls actor B, waiting for a response. In order to handle the request, actor B needs to call actor C. So now A and B's only threads are waiting, and C's is the only one actually giving the CPU any work to do. So much for multi-threading! But this is what you get if you wait for answers all the time.

    Okay, you could increase the number of threads you start in each actor. But you'd be starting them so they could sit around doing nothing. A stack uses up a lot of memory, and context switching can be expensive.

    So it's better to send messages asynchronously, with a callback mechanism so you can pick up the finished result. The problem with your implementation is that you grab another thread from the thread pool, purely to sit around and wait. So you basically apply the workaround of increasing the number of threads. You allocate a thread to the task of never running.

    It would be better to implement PostWithReply in terms of PostWithAsyncReply, i.e. the opposite way round. The asynchronous version is low-level. Building on my delegate-based example (because it involves less typing of code!):

    private bool InsertCoinImpl(int value) 
    {
        // only accept dimes/10p/whatever it is in euros
        return (value == 10);
    }
    
    public void InsertCoin(int value, Action accepted)
    {
        Submit(() => accepted(InsertCoinImpl(value)));
    }
    

    So the private implementation returns a bool. The public asynchronous method accepts an action that will receive the return value; both the private implementation and the callback action are executed on the same thread.

    Hopefully the need to wait synchronously is going to be the minority case. But when you need it, it could be supplied by a helper method, totally general purpose and not tied to any specific actor or message type:

    public static T Wait(Action> activity)
    {
        T result = default(T);
        var finished = new EventWaitHandle(false, EventResetMode.AutoReset);
    
        activity(r =>
            {
                result = r;
                finished.Set();
            });
    
        finished.WaitOne();
        return result;
    }
    

    So now in some other actor we can say:

    bool accepted = Helpers.Wait(r => chocMachine.InsertCoin(5, r));
    

    The type argument to Wait may be unnecessary, haven't tried compiling any of this. But Wait basically magics-up a callback for you, so you can pass it to some asynchronous method, and on the outside you just get back whatever was passed to the callback as your return value. Note that the lambda you pass to Wait still actually executes on the same thread that called Wait.

    We now return you to our regular program...

    As for the actual problem you asked about, you send a message to an actor to get it to do something. Delegates are helpful here. They let you effectively get the compiler to generate you a class with some data, a constructor that you don't even have to call explicitly and also a method. If you're having to write a bunch of little classes, switch to delegates.

    abstract class Actor
    {
        Queue _messages = new Queue();
    
        protected void Submit(Action action)
        {
            // take out a lock of course
            _messages.Enqueue(action);
        }
    
        // also a "run" that reads and executes the 
        // message delegates on background threads
    }
    

    Now a specific derived actor follows this pattern:

    class ChocolateMachineActor : Actor
    {
        private void InsertCoinImpl(int value) 
        {
            // whatever...
        }
    
        public void InsertCoin(int value)
        {
            Submit(() => InsertCoinImpl(value));
        }
    }
    

    So to send a message to the actor, you just call the public methods. The private Impl method does the real work. No need to write a bunch of message classes by hand.

    Obviously I've left out the stuff about replying, but that can all be done with more parameters. (See update above).

提交回复
热议问题