随机超时(TimeSpan)在Rx中不起作用

时间:2014-08-05 08:44:45

标签: c# multithreading system.reactive reactive-programming

之前的帖子似乎不太清楚,所以经过一些测试后,我用更简单的单词重新打开了这篇文章,希望有人可以提供帮助。

我的单身可观察性已从多个来自I / O事件的来源转变,意味着他们同时在基础中被提升,基于测试(证明Rx不是线程安全的)和RX设计指南,我做了序列化,看到lock(...)

public class EventFireCenter
{
    public static event EventHandler<GTCommandTerminalEventArg> OnTerminalEventArrived;
    private static object syncObject = new object();
    public static void TestFireDummyEventWithId(int id)
    {
        lock (syncObject)
        {
            var safe = OnTerminalEventArrived;
            if (safe != null)
            {
                safe(null, new GTCommandTerminalEventArg(id));
            }
        }
    }
}

这是单身人士Observable:

public class UnsolicitedEventCenter
{
    private readonly static IObservable<int> publisher;
    static UnsolicitedEventCenter()
    {
        publisher = Observable.FromEventPattern<GTCommandTerminalEventArg>(typeof(EventFireCenter), "OnTerminalEventArrived")
            .Select(s => s.EventArgs.Id);
    }

    private UnsolicitedEventCenter() { }

    /// <summary>
    /// Gets the Publisher property to start observe an observable sequence. 
    /// </summary>
    public static IObservable<int> Publisher { get { return publisher; } }
}

可以通过以下代码描述Subscribe(...)的方案,您可以看到Subscribe(...)可以在不同的线程中同时调用:

    for (var i = 0; i < concurrentCount; i++)
    {
        var safe = i;
        Scheduler.Default.Schedule(() =>
        {
            IDisposable dsp = null;
            dsp = UnsolicitedEventCenter.Publisher
                .Timeout(TimeSpan.FromMilliseconds(8000))
                .Where(incomingValue => incomingValue == safe)
                .ObserveOn(Scheduler.Default)
                //.Take(1)
                .Subscribe((incomingEvent) =>
                {
                    Interlocked.Increment(ref onNextCalledTimes);
                    dsp.Dispose();
                }
                , ex =>
                {
                    Interlocked.Increment(ref timeoutExceptionOccurredTimes);
                    lock (timedOutEventIds)
                    {
                        // mark this id has been timed out, only for unit testing result check.
                        timedOutEventIds.Add(safe);
                    }

                    dsp.Dispose();
                });
            Interlocked.Increment(ref threadPoolQueuedTaskCount);

        });
    }

正如有经验的人所指出的那样,不建议在Dispose()中拨打OnNext(...),但我们在此处忽略它,因为代码来自制作。

现在问题是.Timeout(TimeSpan.FromMilliseconds(8000))无法正常工作,ex从未被调用,任何人都可以看到代码中的任何异常?

进行测试,我设置了压力测试,但到目前为止,我没有复制它,而在生产中,它每天出现几次。为了以防万一,我粘贴了所有测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Rx
{
    class Program
    {
        static void Main(string[] args)
        {
            // avoid thread creation delay in thread pool.
            ThreadPool.SetMinThreads(200, 50);
            // let the test run for 100 times
            for (int t = 0; t < 100; t++)
            {
                Console.WriteLine("");
                Console.WriteLine("======Current running times: " + t);

                // at meantime, 150 XXX.Subscribe(...) will be called.
                const int concurrentCount = 150;
                // how many fake event will be fire to santisfy that 150 XXX.Subscribe(...).   
                const int fireFakeEventCount = 40;

                int timeoutExceptionOccurredTimes = 0;
                var timedOutEventIds = new List<int>();
                int onNextCalledTimes = 0;
                int threadPoolQueuedTaskCount = 0;

                for (var i = 0; i < concurrentCount; i++)
                {
                    var safe = i;
                    Scheduler.Default.Schedule(() =>
                    {
                        IDisposable dsp = null;
                        dsp = UnsolicitedEventCenter.Publisher
                            .Timeout(TimeSpan.FromMilliseconds(8000))
                            .Where(incomingValue => incomingValue == safe)
                            .ObserveOn(Scheduler.Default)
                            //.Take(1)
                            .Subscribe((incomingEvent) =>
                            {
                                Interlocked.Increment(ref onNextCalledTimes);
                                dsp.Dispose();
                            }
                            , ex =>
                            {
                                Interlocked.Increment(ref timeoutExceptionOccurredTimes);
                                lock (timedOutEventIds)
                                {
                                    // mark this id has been timed out, only for unit testing result check.
                                    timedOutEventIds.Add(safe);
                                }

                                dsp.Dispose();
                            });
                        Interlocked.Increment(ref threadPoolQueuedTaskCount);

                    });
                }

                Console.WriteLine("Starting fire event: " + DateTime.Now.ToString("HH:mm:ss.ffff"));

                int threadPoolQueuedTaskCount1 = 0;
                // simulate a concurrent event fire
                for (int i = 0; i < fireFakeEventCount; i++)
                {
                    var safe = i;
                    Scheduler.Default.Schedule(() =>
                    {
                        EventFireCenter.TestFireDummyEventWithId(safe);
                        Interlocked.Increment(ref threadPoolQueuedTaskCount1);
                    });
                }

                // make sure all proceeding task has been done in threadPool.
                while (threadPoolQueuedTaskCount < concurrentCount)
                {
                    Thread.Sleep(1000);
                }

                // make sure all proceeding task has been done in threadPool.
                while (threadPoolQueuedTaskCount1 < fireFakeEventCount)
                {
                    Thread.Sleep(100);
                }



                Console.WriteLine("Finished fire event: " + DateTime.Now.ToString("HH:mm:ss.ffff"));
                // sleep a time which >3000ms.
                Thread.Sleep(8000);

                Console.WriteLine("timeoutExceptionOccurredTimes: " + timeoutExceptionOccurredTimes);
                Console.WriteLine("onNextCalledTimes: " + onNextCalledTimes);
                if ((concurrentCount - fireFakeEventCount) != timeoutExceptionOccurredTimes)
                {
                    try
                    {
                        Console.WriteLine("Non timeout fired for these ids: " +
                           Enumerable.Range(0, concurrentCount)
                               .Except(timedOutEventIds).Except(Enumerable.Range(0, fireFakeEventCount)).Select(i => i.ToString())
                               .Aggregate((acc, n) => acc + "," + n));
                    }
                    catch (Exception ex) { Console.WriteLine("faild to output timedout ids..."); }
                    break;
                }

                if (fireFakeEventCount != onNextCalledTimes)
                {
                    Console.WriteLine("onNextOccurredTimes assert failed");
                    break;
                }

                if ((concurrentCount - fireFakeEventCount) != timeoutExceptionOccurredTimes)
                {
                    Console.WriteLine("timeoutExceptionOccurredTimes assert failed");
                    break;
                }
            }

            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("DONE!");
            Console.ReadLine();
        }
    }

    public class EventFireCenter
    {
        public static event EventHandler<GTCommandTerminalEventArg> OnTerminalEventArrived;
        private static object syncObject = new object();
        public static void TestFireDummyEventWithId(int id)
        {
            lock (syncObject)
            {
                var safe = OnTerminalEventArrived;
                if (safe != null)
                {
                    safe(null, new GTCommandTerminalEventArg(id));
                }
            }
        }
    }

    public class UnsolicitedEventCenter
    {
        private readonly static IObservable<int> publisher;
        static UnsolicitedEventCenter()
        {
            publisher = Observable.FromEventPattern<GTCommandTerminalEventArg>(typeof(EventFireCenter), "OnTerminalEventArrived")
                .Select(s => s.EventArgs.Id);
        }

        private UnsolicitedEventCenter() { }

        /// <summary>
        /// Gets the Publisher property to start observe an observable sequence. 
        /// </summary>
        public static IObservable<int> Publisher { get { return publisher; } }
    }

    public class GTCommandTerminalEventArg : System.EventArgs
    {
        public GTCommandTerminalEventArg(int id)
        {
            this.Id = id;
        }

        public int Id { get; private set; }
    }
}

1 个答案:

答案 0 :(得分:2)

Timeout很可能没有触发,因为您在 Where过滤器之前已经 。这意味着所有事件都在流经并重置计时器,然后大多数事件都被Where子句过滤掉。对于您订阅的观察者来说,它似乎永远不会得到结果,并且永远不会触发超时。将Timeout移到Where之后,如果他们没有按时获得预期的事件,您现在应该拥有一个超时的系统。