我需要构建一个队列,该队列以非阻塞方式在特定时间点执行与操作相关联的项目。这意味着与该项关联的每个操作都应该在一个单独的线程上执行。
几天前,我偶然发现System.Reactive.Concurrency.IScheduler
接口和内置于System.Reactive
的调度程序。特别引起我兴趣的是Schedule<TState>(TState, TimeSpan, Func<IScheduler, TState, IDisposable>)
方法的Schedule()
重载。
这种重载 - 与 - Scheduler.ThreadPoolScheduler
,Scheduler.TaskPoolScheduler
或NewThreadScheduler
一起似乎是我需要的。
这就是为什么我写了一个快速而简单的程序来比较那些调度程序,你可以在本文的底部找到它们。遗憾的是,对于所有调度程序,一致性检查失败,这意味着某些已安排稍后执行的项目已在先前执行的项目之前执行。
这是由于这些调度程序的性质(启动新线程/任务的时间搞砸了订单吗?)?
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;
}
}
}
答案 0 :(得分:0)
Schedule<TState>(TState, TimeSpan, Func<IScheduler, TState, IDisposable>)
重载会延迟指定时间段内的工作单元。在测试中,您为每个项目指定随机延迟,然后按顺序测试执行顺序。对于小于计时器分辨率的延迟,工作将立即安排。
如果您想要执行顺序,可以使用EventLoopScheduler
。