【问题标题】:Console.ReadLine() passed to C# eventConsole.ReadLine() 传递给 C# 事件
【发布时间】:2015-07-13 06:31:08
【问题描述】:

我正在学习 RX,并希望使用 Console.ReadLine 作为可观察序列的来源。

我知道我可以使用“yield return”创建“IEnumerable”,但对于我的具体用例,我决定创建一个 C# 事件,以便潜在的许多观察者能够共享相同的键盘输入。

这是我的代码:

class Program
{
    private delegate void OnNewInputLineHandler(string line);

    private static event OnNewInputLineHandler OnNewInputLineEvent = _ => {};

    static void Main(string[] args)
    {
        Task.Run((Action) GetInput);

        var input = ConsoleInput();
        input.Subscribe(s=>Console.WriteLine("1: " + s));

        Thread.Sleep(30000);
    }

    private static void GetInput()
    {
        while (true)
            OnNewInputLineEvent(Console.ReadLine());
    }

    private static IObservable<string> ConsoleInput()
    {
        return Observable.Create<string>(
        (IObserver<string> observer) =>
        {
            OnNewInputLineHandler h = observer.OnNext;
            OnNewInputLineEvent += h;
            return Disposable.Create(() => { OnNewInputLineEvent -= h; });
        });
    }
}

我的问题 - 当我运行如上所示的 GetInput 方法时,第一行输入没有发送到序列(而是发送到事件处理程序)。

但是,如果我用以下版本替换它,一切都会按预期工作:

private static void GetInput()
{
    while (true)
    {
        var s = Console.ReadLine();
        OnNewInputLineEvent(s);
    }
}

有人能解释一下为什么会发生这种情况吗?

【问题讨论】:

    标签: events console system.reactive readline


    【解决方案1】:

    你试图让自己的生活变得困难。几乎总有一种方法可以让 Rx 变得简单。这只是学习更多地从功能上而非程序上思考的问题。

    这就是你所需要的:

    class Program
    {
        static void Main(string[] args)
        {
            var subscription = ConsoleInput().Subscribe(s => Console.WriteLine("1: " + s));
            Thread.Sleep(30000);
            subscription.Dispose();
        }
    
        private static IObservable<string> ConsoleInput()
        {
            return
                Observable
                    .FromAsync(() => Console.In.ReadLineAsync())
                    .Repeat()
                    .Publish()
                    .RefCount()
                    .SubscribeOn(Scheduler.Default);
        }
    }
    

    这允许多个订阅者通过.Publish().RefCount() 共享一个输入。 .SubscribeOn(Scheduler.Default) 将订阅推送到新线程 - 没有它,您会阻止订阅。

    【讨论】:

    • 感谢您的提示!然而它并没有回答我的问题 - 为什么一个版本有效: var s = Console.ReadLine(); OnNewInputLineEvent(s);而另一个丢失第一个通知: OnNewInputLineEvent(Console.ReadLine());
    • @Natan - 我要猜测比赛条件,但很难证明。生成的 IL 非常相似 - 首先将字符串存储在变量中。它可能只需要更长的时间。对我来说似乎有点狡猾。
    • 这也是我的第一个想法。我还浏览了 IL 代码,但无法辨别为什么这会有所作为。但如果这是一个竞赛条件,它就不会那么一致了!如果我直接运行或在调试器中运行,调试和发布版本中的行为是相同的。我想我需要构建 RX 并尝试调试,但我不确定这是否值得。
    • 不错的答案。我认为Main 的第一行有一个额外的括号,并且缺少分号
    【解决方案2】:

    如果您将Task.Run((Action) GetInput); 移至订阅后,您的代码将按需要运行。这是因为在您的原始版本中,OnNewInputEvent(Console.ReadLine()) 的第一次调用是在您将 OnNewInputLineEvent 挂钩到 observer.OnNext 之前运行的。

    【讨论】:

    • 正确!两个版本之间的区别在于,在var s = ... 版本中,GetInput 挂起等待输入,并且订阅发生在对事件字段的任何引用之前。在短版event(Console.ReadLine()) 中,编译器首先复制事件字段的值(此时该值只有一个非Rx 订阅者),然后调用ReadLine()。即使订阅发生在输入第一行之前,编译器使用的值也是前一个。
    猜你喜欢
    • 1970-01-01
    • 2020-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多