给定附加的LINQ-Pad代码片段。
它创建了8个任务,执行500毫秒并绘制图形以显示线程实际运行的时间。
现在,如果我在线程循环中添加Thread.Sleep或Task.Delay,我可以可视化Windows系统计时器的时钟(约15ms):
现在,还有一个名为timeBeginPeriod
的函数,可以降低系统计时器的分辨率(例如降至1毫秒)。这里就有了区别。使用Thread.Sleep
会得到以下图表(符合预期):
使用Task.Delay
时,我得到的图形与将时间设置为15ms时相同:
问题: 为什么TPL会忽略计时器设置?
这是代码(你需要LinqPad 5.28 beta来运行图表)
void Main()
{
const int Threads = 8;
const int MaxTask = 20;
const int RuntimeMillis = 500;
const int Granularity = 10;
ThreadPool.SetMinThreads(MaxTask, MaxTask);
ThreadPool.SetMaxThreads(MaxTask, MaxTask);
var series = new bool[Threads][];
series.Initialize(i => new bool[RuntimeMillis * Granularity]);
Watch.Start();
var tasks = Async.Tasks(Threads, i => ThreadFunc(series[i], pool));
tasks.Wait();
series.ForAll((x, y) => series[y][x] ? new { X = x / (double)Granularity, Y = y + 1 } : null)
.Chart(i => i.X, i => i.Y, LINQPad.Util.SeriesType.Point)
.Dump();
async Task ThreadFunc(bool[] data, Rendezvous p)
{
double now;
while ((now = Watch.Millis) < RuntimeMillis)
{
await Task.Delay(10);
data[(int)(now * Granularity)] = true;
}
}
}
[DllImport("winmm.dll")] internal static extern uint timeBeginPeriod(uint period);
[DllImport("winmm.dll")] internal static extern uint timeEndPeriod(uint period);
public class Rendezvous
{
private readonly object lockObject = new object();
private readonly int max;
private int n = 0;
private readonly ManualResetEvent waitHandle = new ManualResetEvent(false);
public Rendezvous(int count)
{
this.max = count;
}
public void Join()
{
lock (this.lockObject)
{
if (++this.n >= max)
waitHandle.Set();
}
}
public void Wait()
{
waitHandle.WaitOne();
}
public void Reset()
{
lock (this.lockObject)
{
waitHandle.Reset();
this.n = 0;
}
}
}
public static class ArrayExtensions
{
public static void Initialize<T>(this T[] array, Func<int, T> init)
{
for (int n = 0; n < array.Length; n++)
array[n] = init(n);
}
public static IEnumerable<TReturn> ForAll<TValue, TReturn>(this TValue[][] array, Func<int, int, TReturn> func)
{
for (int y = 0; y < array.Length; y++)
{
for (int x = 0; x < array[y].Length; x++)
{
var result = func(x, y);
if (result != null)
yield return result;
}
}
}
}
public static class Watch
{
private static long start;
public static void Start() => start = Stopwatch.GetTimestamp();
public static double Millis => (Stopwatch.GetTimestamp() - start) * 1000.0 / Stopwatch.Frequency;
public static void DumpMillis() => Millis.Dump();
}
public static class Async
{
public static Task[] Tasks(int tasks, Func<int, Task> thread)
{
return Enumerable.Range(0, tasks)
.Select(i => Task.Run(() => thread(i)))
.ToArray();
}
public static void JoinAll(this Thread[] threads)
{
foreach (var thread in threads)
thread.Join();
}
public static void Wait(this Task[] tasks)
{
Task.WaitAll(tasks);
}
}
timeBeginPeriod
似乎是一个 Windows 全局设置,参见 https://msdn.microsoft.com/en-us/library/windows/desktop/dd757624(v=vs.85).aspx。当您启动一个仅设置timeBeginPeriod
的应用程序,然后在另一个应用程序中再次运行您的任务示例时会发生什么? - Dennis KuypersSystem.Threading.Timer
,根据 MSDN 的说法,应该遵守系统时钟。 - Tho MaitimeBeginPeriod
。 - Kevin Gosse