Skip to content

Instantly share code, notes, and snippets.

@AArnott
Last active March 1, 2021 21:28
Show Gist options
  • Save AArnott/066b065c4408b7c5fb252dcfa538dedb to your computer and use it in GitHub Desktop.
Save AArnott/066b065c4408b7c5fb252dcfa538dedb to your computer and use it in GitHub Desktop.
Demonstrates scheduling unbounded work and applying a throttle to keep the threadpool responsive.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("*** WITHOUT throttling ***");
MeasureWork(false);
Console.WriteLine("*** WITH throttling ***");
MeasureWork(true);
}
private static void MeasureWork(bool throttle)
{
TaskScheduler scheduler = throttle
? new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, Environment.ProcessorCount * 2).ConcurrentScheduler
: TaskScheduler.Default;
Console.WriteLine("Race has begun.");
var sw = Stopwatch.StartNew();
var workerTasks = new Task[800];
for (int i = 0; i < workerTasks.Length; i++)
{
workerTasks[i] = Task.Factory.StartNew(Work, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
Task allFinished = Task.WhenAll(workerTasks);
allFinished.ContinueWith(_ => sw.Stop());
while (!allFinished.IsCompleted)
{
Thread.Sleep(500);
Console.WriteLine("Threadpool responded in: " + MeasureThreadPoolResponsiveness());
}
Console.WriteLine($"FINISHED in {sw.Elapsed}");
}
static void Work()
{
SpinWait.SpinUntil(() => false, 40);
}
static TimeSpan MeasureThreadPoolResponsiveness()
{
var sw = Stopwatch.StartNew();
Task.Run(() => sw.Stop()).Wait();
return sw.Elapsed;
}
}
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("*** WITHOUT throttling ***");
MeasureWork(false);
Console.WriteLine("*** WITH throttling ***");
MeasureWork(true);
}
private static void MeasureWork(bool throttled)
{
SemaphoreSlim semaphore = throttled ? new SemaphoreSlim(Environment.ProcessorCount * 2) : null;
Console.WriteLine("Race has begun.");
var sw = Stopwatch.StartNew();
var workerTasks = new Task[800];
for (int i = 0; i < workerTasks.Length; i++)
{
workerTasks[i] = Task.Run(async delegate
{
if (semaphore != null)
{
await semaphore.WaitAsync();
}
await WorkAsync();
semaphore?.Release();
});
}
Task allFinished = Task.WhenAll(workerTasks);
allFinished.ContinueWith(_ => sw.Stop());
while (!allFinished.IsCompleted)
{
Thread.Sleep(500);
Console.WriteLine("Threadpool responded in: " + MeasureThreadPoolResponsiveness());
}
Console.WriteLine($"FINISHED in {sw.Elapsed}");
}
static async Task WorkAsync()
{
SpinWait.SpinUntil(() => false, 20);
await Task.Yield();
SpinWait.SpinUntil(() => false, 20);
}
static TimeSpan MeasureThreadPoolResponsiveness()
{
var sw = Stopwatch.StartNew();
Task.Run(() => sw.Stop()).Wait();
return sw.Elapsed;
}
}
@AArnott
Copy link
Author

AArnott commented May 11, 2017

Good point, @binki. That would explain why the throttled variety ran mysteriously faster.
With your change and output that shows throttling made it slower, I suspect some optimization or picking a more efficient scheduler, or a different concurrency level than I did would reduce the difference between the two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment