System.Reactive Schedulers DueTime调度

时间:2016-12-22 14:06:16

标签: .net multithreading system.reactive

我需要构建一个队列,该队列以非阻塞方式在特定时间点执行与操作相关联的项目。这意味着与该项关联的每个操作都应该在一个单独的线程上执行。

几天前,我偶然发现System.Reactive.Concurrency.IScheduler接口和内置于System.Reactive的调度程序。特别引起我兴趣的是Schedule<TState>(TState, TimeSpan, Func<IScheduler, TState, IDisposable>)方法的Schedule()重载。

这种重载 - 与 - Scheduler.ThreadPoolSchedulerScheduler.TaskPoolSchedulerNewThreadScheduler一起似乎是我需要的。

这就是为什么我写了一个快速而简单的程序来比较那些调度程序,你可以在本文的底部找到它们。遗憾的是,对于所有调度程序,一致性检查失败,这意味着某些已安排稍后执行的项目已在先前执行的项目之前执行。

这是由于这些调度程序的性质(启动新线程/任务的时间搞砸了订单吗?)?

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

namespace RxDueTimeScheduling
{
    public class ScheduledItem
    {
        public TimeSpan Delay;
        public DateTime EnqueueTime;
        public DateTime DueTime;        
        public double Fault;
        public double ExecutionTime;
    }

class Program
{
    private static AutoResetEvent mWait = new AutoResetEvent(false);
    private static long mEnqueueingFinishedAfter;
    private static int mMinDelay = 10;
    private static int mMaxDelay = 500;
    private static int mMessagesPerTest = 50;
    private static List<ScheduledItem> mResultList;
    private static Stopwatch mTestStopWatch = new Stopwatch();
    private static Stopwatch mExecutionStopWatch = new Stopwatch();
    private static object mLockObject =  new Object();
    private static string mFormatString = "mm:ss:fffff"; // ATTENTION: not displaying hours to make output fit into one line in console
    private static volatile int mCounter;
    private static volatile int mLogCounter;

    static void Main(string[] args)
    {
        var testData = CreateTestData(mMinDelay, mMaxDelay,    mMessagesPerTest);
        RunTest(Scheduler.NewThread, testData);            
        RunTest(Scheduler.TaskPool,testData);
        RunTest(Scheduler.ThreadPool, testData);

        Console.WriteLine();
        Console.WriteLine("All Tests finished. Press Enter to exit");
        Console.ReadLine();

    }


    private static void RunTest(IScheduler scheduler, List<int> testData)
    {
        Thread.Sleep(1000);
        Console.WriteLine();
        Console.WriteLine("------- new test with " + scheduler.GetType().Name + " -------------");
        mCounter = 1;
        mLogCounter = 0;            
        mEnqueueingFinishedAfter = 0;
        mResultList = new List<ScheduledItem>();            
        Stopwatch w = new Stopwatch();
        mTestStopWatch.Restart();
        w.Restart();           
        testData.ForEach(d =>
        {
            var now = DateTime.Now;
            var delay = TimeSpan.FromMilliseconds(d);
            var item = new ScheduledItem()
            {
                Delay = delay,
                EnqueueTime = now,
                DueTime = now + delay,                    
            };

            scheduler.Schedule(item, item.Delay, myActionFunc);                
        });
        w.Stop();
        mEnqueueingFinishedAfter = w.ElapsedMilliseconds;
        if (!mWait.WaitOne(mMaxDelay*mMessagesPerTest))
        {
            Console.WriteLine("Could not finish test {0} in {1} seconds. Messages Processed {2}", scheduler.GetType().Name, (mMaxDelay*mMessagesPerTest/1000), mResultList.Count );
            PrintStats(scheduler);
        }
    }


    private static List<int> CreateTestData(int minDelay, int maxDelay, int count)
    {
        var result = new List<int>();
        Random r = new Random();
        for(int i = 0; i < count; i++)
        {
            var nextDelay = r.Next(minDelay, maxDelay);
            result.Add(nextDelay);
        }
        return result;
    }

    private static Func<IScheduler, ScheduledItem, IDisposable> myActionFunc = new Func<IScheduler, ScheduledItem, IDisposable>(OnDequeued);


    private static IDisposable OnDequeued(IScheduler scheduler, ScheduledItem item)
    {
        DateTime? now = null;
        lock (mLockObject)            
        {

            mExecutionStopWatch.Restart();            
            now = DateTime.Now;
            item.Fault = (now.Value - item.DueTime).TotalMilliseconds;
            mResultList.Add(item);
        }


            Console.WriteLine(
                mLogCounter++ +
                ":{0} scheduled on {1} for execution on {2}, fault in ms: {3} delay in ms:{4}, thread: {5}",
                now.Value.ToString(mFormatString),
                item.EnqueueTime.ToString(mFormatString),
                item.DueTime.ToString(mFormatString),
                item.Fault,
                item.Delay.TotalMilliseconds,
                Thread.CurrentThread.ManagedThreadId);
            if (mCounter++ == mMessagesPerTest)
            {
                mTestStopWatch.Stop();
                PrintStats(scheduler);
                mWait.Set();
            }
            mExecutionStopWatch.Stop();
            item.ExecutionTime = mExecutionStopWatch.ElapsedMilliseconds;
            return null;            
    }

    private static void PrintStats(IScheduler scheduler)
    {
        var consistencyErrors = ConsistencyCheck(mResultList);
        string consistencyResultString = Environment.NewLine + (consistencyErrors.Count == 0
                                            ? "Consistency Check passed."
                                            : "Consistency Errors item: " + string.Join(",",consistencyErrors).TrimEnd(','));
        Console.WriteLine();
        Console.WriteLine(String.Format("Test: {0} Duration: {1} Enqueueing: {2} Average Fault: {3} Max Fault: {4} Cosistency Check Result: {5}, Average ExeTime: {6} Max ExeTime: {7}",
                                        scheduler.GetType().Name,
                                        mTestStopWatch.ElapsedMilliseconds,
                                        mEnqueueingFinishedAfter,
                                        (int) mResultList.Average(r => r.Fault),
                                        (int) mResultList.Max(r => r.Fault),
                                        consistencyResultString,
                                        mResultList.Average(r => r.ExecutionTime),
                                        mResultList.Max(r => r.ExecutionTime)
                              ));
    }

    private static List<int> ConsistencyCheck(List<ScheduledItem> resultList)
    {
        var result = new List<int>();
        for(int i = 1; i<resultList.Count;i++)
        {
            var item = resultList[i];
            var previousItem = resultList[i-1];

            if (item.DueTime < previousItem.DueTime)
            {
                result.Add(i);
            }
        }
        return result;
    }
}

}

1 个答案:

答案 0 :(得分:0)

Schedule<TState>(TState, TimeSpan, Func<IScheduler, TState, IDisposable>)重载会延迟指定时间段内的工作单元。在测试中,您为每个项目指定随机延迟,然后按顺序测试执行顺序。对于小于计时器分辨率的延迟,工作将立即安排。

如果您想要执行顺序,可以使用EventLoopScheduler