This was the tricky part. It took me a while to realize that I couldn't safely unload the AppDomain because the loop in StartListening was still running (among other things). So we generate a cancellation token and use that to exit the loop in StartListening.