I have worked out a code for graceful shutdown of a httplistener. However, I am not sure if its foolproof. Below I provide c# code and the powershell (v3.0) scripts I've used to run my tests...
C# Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace caHttpServer
{
class Program
{
static void Main(string[] args)
{
HttpListener server = new HttpListener() {
IgnoreWriteExceptions = true //really not much useful
};
server.Prefixes.Add("http://*:8080/");
server.Start();
AutoResetEvent waiter = new AutoResetEvent(false);
int counter = 0;
AsyncCallback OnContextReceived = null;
bool StopRequested = false;
Console.WriteLine("MainThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
Action AcceptContext = new Action(() => {
try
{
server.BeginGetContext(OnContextReceived, null);
}
catch (Exception e)
{
Console.WriteLine("{0}: Error: {0}", Thread.CurrentThread.ManagedThreadId, e.Message);
}
});
OnContextReceived = new AsyncCallback(ar => {
int id = Thread.CurrentThread.ManagedThreadId;
if (server.IsListening)
{
AcceptContext();
}
HttpListenerContext ctx = null;
try
{
ctx = server.EndGetContext(ar);
}
catch (Exception e)
{
Console.WriteLine("{0}: Error: {1}", id, e.Message);
}
if (ctx != null)
{
if (!StopRequested)
{
Interlocked.Increment(ref counter);
Console.WriteLine("{0} Processing: {1}", id, ctx.Request.RawUrl);
var response = ctx.Response.OutputStream;
int sleep = int.Parse(ctx.Request.QueryString["sleep"]);
Thread.Sleep(sleep);
try
{
using (var sw = new StreamWriter(response))
{
sw.WriteLine("{0} : Hello World", DateTime.Now.ToLongTimeString());
}
ctx.Response.Close();
}
catch (Exception e)
{
Console.WriteLine("{0} : Error: {1}", id, e.Message);
}
Interlocked.Decrement(ref counter);
}
else
{
ctx.Response.Close();
}
}
});
AcceptContext();
Console.WriteLine("Listening for connections...");
Console.CancelKeyPress += (s, evt) =>
{
evt.Cancel = true;
StopRequested = true;
waiter.Set();
};
waiter.WaitOne();
//waiter.WaitOne(10000);
//waiter.Reset();
Console.WriteLine("Waiting for completion...");
var task = Task.Factory.StartNew(() => {
//I cannot get rid of this contraption...
while (counter > 0)
{
Console.WriteLine("Active: {0}", counter);
Thread.Sleep(2000);
}
});
task.Wait();
server.Close();
Console.WriteLine("Stopped");
}
}
}
Powershell script:
$cmd = {
param($sleep)
$url = "http://localhost:8080/?sleep=$sleep"
write-host $url
Invoke-RestMethod $url
}
$foo = "foo"
$rnd = new-object System.Random
$jobs = @()
get-job | remove-job
0..5 | ForEach-Object {
$sleep = $rnd.next(1, 10) * 1000;
$jobs += Start-Job -ScriptBlock $cmd -ArgumentList $sleep;
}
write-host "waiting..."
$jobs | wait-job
$jobs | receive-job
There is no control about when to spawn the powershell backgroundjob. Or maybe there is...Which is why I commented the line (in c#) which has the timeout wait. I manually ran the program...then the script. After I saw some output in my program...I hit Ctrl + C
to signal the shutdown.