简体   繁体   中英

IObservable ObserveOn is locking the Thread, is this preventable?

I'm designing a server that diverts client requests to a single thread dedicated to handling data. I do this to prevent any race conditions or concurrency issues with the data being handled. Because the server is designed to be reactive, whenever the server receives a request I use Observables to notify the rest of the program of the request. Now because the server socket is listening and emitting signals from multiple threads I want to ensure that the observables, no matter what thread the server emits on, would always be observed on the dedicated data handling thread. I opted to use the ObserveOn method and this immediately backfired. I noticed immediately that upon one observable firing, none of the others were firing. Not only that, but other actions sent to the dedicated thread weren't firing either.

Essentially, the observable seems to be "claiming" the thread for itself. The thread is completely blocked by the observable and cannot be used for anything else at all other than that observable's emissions. I don't want this happening because this thread is dedicated to all data handling operations, and this is stopping me from using the thread for any other observables or future data handling tasks. So, what are my options here to prevent the observable locking the thread down to itself, or to force observation of observables to my dedicated thread without blocking out other observables.

This example code demonstrates the problem. Here we use a single threaded task scheduler and notice that it operates just fine until the first subject, which has been set to ObserveOn the scheduler, emits it's string. After this happens, no further subject or action fires. The first subject effectively locked the thread down for itself.

public static class Program
{
    static void Main(string[] args)
    {
        //Within the Tester class we setup a single threaded task scheduler that will be handling all of these methods
        var _t = new Tester();

        string _string = "Hello World";

        //These three will print their string to the console
        _t.PrintDirectlyWithAction(_string);//Succeeds
        _t.PrintDirectlyWithAction(_string);//Succeeds
        _t.PrintDirectlyWithAction(_string);//Succeeds

        //Only subject 1 will emit and print it's string, the other two fail
        _t.PrintThroughSubject1(_string);//Succeeds
        _t.PrintThroughSubject2(_string);//Fails
        _t.PrintThroughSubject3(_string);//Fails

        _t.PrintDirectlyWithAction(_string);//Fails
        _t.PrintDirectlyWithAction(_string);//Fails
        _t.PrintDirectlyWithAction(_string);//Fails

        //We essentially can't do anything with the thread after subject 1 observed on it

        Console.ReadLine();
    }

    public class Tester
    {
        TaskFactory tf;
        TaskPoolScheduler pool;
        int _actionCount = 0;
        Subject<string> s1 = new Subject<string>();
        Subject<string> s2 = new Subject<string>();
        Subject<string> s3 = new Subject<string>();

        public Tester()
        {
            //We're create a task pool that uses a single threaded concurrent task scheduler
            var _scheduler = new ConcurrentExclusiveSchedulerPair();
            tf = new TaskFactory(_scheduler.ExclusiveScheduler);
            pool = new TaskPoolScheduler(tf);

            //And then we set the subjects to each be observed on the single threaded scheduler
            s1.ObserveOn(pool).Subscribe(_s => Console.WriteLine(
                $"Subject (1) says \"{_s}\" - on thread {Thread.CurrentThread.ManagedThreadId}"));
            s2.ObserveOn(pool).Subscribe(_s => Console.WriteLine(
                $"Subject (2) says \"{_s}\" - on thread {Thread.CurrentThread.ManagedThreadId}"));
            s3.ObserveOn(pool).Subscribe(_s => Console.WriteLine(
                $"Subject (3) says \"{_s}\" - on thread {Thread.CurrentThread.ManagedThreadId}"));
        }

        public void PrintThroughSubject1(string _string)
        {
            s1.OnNext(_string);
        }

        public void PrintThroughSubject2(string _string)
        {
            s2.OnNext(_string);
        }

        public void PrintThroughSubject3(string _string)
        {
            s3.OnNext(_string);
        }

        public void PrintDirectlyWithAction(string _string)
        {
            //This is here to demonstrate that the single threaded task scheduler accepts actions just fine
            //and can handle them in sequence
            tf.StartNew(() =>
            {
                Console.WriteLine(
                    $"Direct action ({_actionCount++}) says \"{_string}\" - on thread {Thread.CurrentThread.ManagedThreadId}");
            });
        }

    }
}

TL;DR: I need to be able to force multiple observables emissions to be observed on a specific thread, but RxNet seems to only be letting a single subject be observed on a thread and nothing else can. How can I circumvent this to observe multiple observables on the same thread?

I might have over complicated it. EventLoopScheduler might be what you need.

Try this:

public static class Program
{
    static void Main(string[] args)
    {
        //Within the Tester class we setup a single threaded task scheduler that will be handling all of these methods
        var _t = new Tester();

        string _string = "Hello World";

        //These three will print their string to the console
        _t.PrintDirectlyWithAction(_string);//Succeeds
        _t.PrintDirectlyWithAction(_string);//Succeeds
        _t.PrintDirectlyWithAction(_string);//Succeeds

        //Only subject 1 will emit and print it's string, the other two fail
        _t.PrintThroughSubject1(_string);//Succeeds
        _t.PrintThroughSubject2(_string);//Fails
        _t.PrintThroughSubject3(_string);//Fails

        _t.PrintDirectlyWithAction(_string);//Fails
        _t.PrintDirectlyWithAction(_string);//Fails
        _t.PrintDirectlyWithAction(_string);//Fails

        //We essentially can't do anything with the thread after subject 1 observed on it

        Console.ReadLine();
    }

    public class Tester
    {
        private EventLoopScheduler els = new EventLoopScheduler();
        int _actionCount = 0;
        Subject<string> s1 = new Subject<string>();
        Subject<string> s2 = new Subject<string>();
        Subject<string> s3 = new Subject<string>();

        public Tester()
        {
            //We're create a task pool that uses a single threaded concurrent task scheduler


            //And then we set the subjects to each be observed on the single threaded scheduler
            s1.ObserveOn(els).Subscribe(_s => Console.WriteLine(
                $"Subject (1) says \"{_s}\" - on thread {Thread.CurrentThread.ManagedThreadId}"));
            s2.ObserveOn(els).Subscribe(_s => Console.WriteLine(
                $"Subject (2) says \"{_s}\" - on thread {Thread.CurrentThread.ManagedThreadId}"));
            s3.ObserveOn(els).Subscribe(_s => Console.WriteLine(
                $"Subject (3) says \"{_s}\" - on thread {Thread.CurrentThread.ManagedThreadId}"));
        }

        public void PrintThroughSubject1(string _string)
        {
            s1.OnNext(_string);
        }

        public void PrintThroughSubject2(string _string)
        {
            s2.OnNext(_string);
        }

        public void PrintThroughSubject3(string _string)
        {
            s3.OnNext(_string);
        }

        public void PrintDirectlyWithAction(string _string)
        {
            //This is here to demonstrate that the single threaded task scheduler accepts actions just fine
            //and can handle them in sequence
            els.Schedule(() =>
            {
                Console.WriteLine(
                    $"Direct action ({_actionCount++}) says \"{_string}\" - on thread {Thread.CurrentThread.ManagedThreadId}");
            });
        }

    }
}

I get this result:

Direct action (0) says "Hello World" - on thread 17
Direct action (1) says "Hello World" - on thread 17
Direct action (2) says "Hello World" - on thread 17
Subject (1) says "Hello World" - on thread 17
Subject (2) says "Hello World" - on thread 17
Subject (3) says "Hello World" - on thread 17
Direct action (3) says "Hello World" - on thread 17
Direct action (4) says "Hello World" - on thread 17
Direct action (5) says "Hello World" - on thread 17

Don't forget to .Dispose() your EventLoopScheduler when you're done.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM