I am new with async task in C# 4.5 and feel insecure about this:
I want to create a Windows Service that listen messages from a network (similar to msmq for example), when a message is received, do stuff not CPU bound (call webservices and rest API) without blocking other messages.
I created this console application in order to test the concept:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Sandbox
{
public class Program
{
static CancellationTokenSource cts;
static CancellationToken ct;
static TaskCompletionSource<bool> maintask;
static void Main(string[] args)
{
Console.WriteLine(string.Format("[{0:000}] Start.", Thread.CurrentThread.ManagedThreadId));
cts = new CancellationTokenSource();
ct = cts.Token;
// Run inside a new thread, the service will have 3 dedicated threads
maintask = new TaskCompletionSource<bool>(
Task.Factory.StartNew(() =>
{
Run(ct);
maintask.SetResult(true);
},
TaskCreationOptions.LongRunning));
Console.WriteLine("Press a key to stop.");
Console.ReadKey();
cts.Cancel(); // Stop the main threads
maintask.Task.Wait(); // Wait for tasks to end properly
}
static List<Task> tasks;
/// <summary>Main loop</summary>
static void Run(CancellationToken cancel)
{
Console.WriteLine(string.Format("[{0:000}] Start loop.", Thread.CurrentThread.ManagedThreadId));
tasks = new List<Task>();
int iteration = 0;
while (!ct.IsCancellationRequested)
{
string onemsg = WaitMessageFromNetwork();
// for the test, "randomize" duration of async task.
iteration += 100;
int wait;
switch (iteration)
{
case 200: wait = 1000; break;
case 300: wait = 2000; break;
case 400: wait = 1500; break;
case 500: wait = 1600; break;
case 600: wait = 1100; break;
case 700: wait = 1200; break;
case 800: wait = 1900; break;
case 900: wait = 1800; break;
case 1000: wait = 800; break;
case 1100: wait = 900; break;
default: wait=0; break;
}
if (wait > 0) tasks.Add(DoWorkAsync(wait, onemsg));
foreach (Task t in tasks.Where(x => x.IsFaulted))
{
Console.WriteLine(t.Exception); // log.error(...
}
tasks.RemoveAll(x => x.IsCanceled | x.IsCompleted | x.IsFaulted); // Remove finished tasks
// Limit asynchronism to 5 tasks for the test, unnecessary ? X in real service ?
if (tasks.Count > 4) Console.WriteLine("WaitAny returns {0}.", Task.WaitAny(tasks.ToArray()));
}
Task.WaitAll(tasks.ToArray()); // If the service stop, wait pending tasks to finish.
}
static string WaitMessageFromNetwork()
{
Thread.Sleep(100); // Real method will not block the thread
return "dummy";
}
/// <summary>Do I/O bound stuff : call WebServices async and REST API.</summary>
static async Task DoWorkAsync(int wait, string onemsg)
{
try
{
Console.WriteLine(string.Format("[{0:000}] START {1:0000}.", Thread.CurrentThread.ManagedThreadId, wait));
await Task.Delay(wait);
Console.WriteLine(string.Format("[{0:000}] END {1:0000}.", Thread.CurrentThread.ManagedThreadId, wait));
}
catch(Exception ex)
{
Console.WriteLine(ex); // log.error(...
}
}
}
}
- It works but is it OK to do like this?
- In my tests, START and END messages are not one the same thread (not a problem in my case), it’s normal if I understand because a console or service application does not have a SynchronizationContext ?