Skip to content

Instantly share code, notes, and snippets.

@tayl0r
Last active August 29, 2015 14:00
Show Gist options
  • Save tayl0r/11139132 to your computer and use it in GitHub Desktop.
Save tayl0r/11139132 to your computer and use it in GitHub Desktop.
c# command line app - threading & async
static void Main(string[] args) {
// this news a new instance of TournamentService/BaseService,
// which runs StartTickLoop, which creates the new threads
BaseService.OnStart(true);
Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) {
Console.WriteLine("captured ctrl-c, setting close flag");
e.Cancel = true;
CommandLineApp.close = true;
};
// main thread run loop
while (BaseService.IsRunning()) {
//Console.WriteLine("tick 2");
System.Threading.Thread.Sleep((int)BaseService.TICK_TIME);
if (CommandLineApp.close) {
Console.WriteLine("close flag set, ending gracefully");
BaseService.OnStop();
}
}
Console.WriteLine("Press any key to close...");
Console.ReadLine();
}
public class BaseService {
ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
CancellationTokenSource _cancellationTokenSouce = new CancellationTokenSource();
static List<Thread> _threads = new List<Thread>();
public string _name;
#if TOURNAMENT_PROCESSOR
public const long TICK_TIME = 5000;
#endif
#region static to handle the creation and start of this service
static BaseService _proc;
public static void OnStart(bool writeToConsole) {
// this is how i do different types of commandline/services
// they share the same base code & same VS project (different build configuration)
#if TOURNAMENT_PROCESSOR
_proc = new TournamentService(writeToConsole);
#endif
_proc.StartTickLoop();
}
public static bool IsRunning() {
foreach (var thread in _threads) {
if (thread.IsAlive == true) {
return true;
}
if (thread.ThreadState != System.Threading.ThreadState.Stopped) {
return true;
}
}
return false;
}
public void StartTickLoop() {
#if TOURNAMENT_PROCESSOR
// multiple threads
foreach (var kvp in LeaderboardManager.TOURNAMENT_CONFIG_DATA) {
var theme = kvp.Key;
var config = kvp.Value;
var thread = new Thread(ThreadStart);
thread.Name = string.Format("Thread_{0}", theme.ToString());
thread.IsBackground = true;
thread.Start(theme);
_threads.Add(thread);
}
#else
// single thread
var thread = new Thread(ThreadStart);
thread.Name = "Thread";
thread.IsBackground = true;
thread.Start();
_threads.Add(thread);
#endif
}
public void StopTickLoop() {
Logger.L("stop tick loop");
_shutdownEvent.Set();
_cancellationTokenSouce.Cancel();
// give the thread some time to stop (900,000 ms = 15 minutes
foreach (var thread in _threads) {
if (!thread.Join(900000)) {
thread.Abort();
}
}
}
void ThreadStart(object data) {
var stopwatch = new Stopwatch();
#if TOURNAMENT_PROCESSOR
var theme = (SlotMachineThemeType)data;
var name = theme.ToString();
var child = new TournamentChildService(theme);
#endif
// per thread run loop
while (!_shutdownEvent.WaitOne(0)) {
stopwatch.Restart();
//await Logger.LA("Tick");
//Console.WriteLine("tick 1");
#if TOURNAMENT_PROCESSOR
child.Tick(_cancellationTokenSouce.Token);
#else
Tick();
#endif
if (stopwatch.ElapsedMilliseconds < TICK_TIME) {
var waitTime = TICK_TIME - stopwatch.ElapsedMilliseconds;
Logger.L(string.Format("########## {1}: Waiting {0:N2} seconds for next tick.", waitTime/1000, name));
Thread.Sleep((int)(waitTime));
} else {
Logger.L(string.Format("########## {1}: tick time: {0:N2}s", stopwatch.Elapsed.TotalSeconds, name));
}
}
Logger.L("Stopped: " + name + ", " + Thread.CurrentThread.Name + ", " + Thread.CurrentThread.ManagedThreadId);
}
protected virtual void Tick() {
// single thread service uses this Tick
// multiple thread service (like our TournamentProcessor uses the child service Tick)
}
class TournamentService : BaseService {
public TournamentService(bool writeToConsole)
#if RELEASE
: base("TournamentProcessor", writeToConsole) {
#else
: base("TournamentProcessor-Dev", writeToConsole) {
#endif
}
}
class TournamentChildService {
TournamentProcessor _t;
private SlotMachineThemeType _theme;
public TournamentChildService(SlotMachineThemeType theme) {
_theme = theme;
LeaderboardManager.CreateTournamentsForTheme(_theme).Wait();
// wait 5 seconds so dynamo db indexes have a chance to catch up (since reads from a global secondary index are forced to be eventually consistant)
Thread.Sleep(5000);
_t = new TournamentProcessor(_theme);
}
public void Tick(CancellationToken cancellationToken) {
try {
// this is how i'm going from non-async to async mode
// pass in the cancellation token so in our 5+ minute sleep we can break out via ctrl-c
// in TickAsync, everything stays with async methods and i handle all exceptions inside
_t.TickAsync(cancellationToken).Wait();
} catch (Exception) {
if (cancellationToken.IsCancellationRequested == false) {
throw;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment