1
\$\begingroup\$

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 ?
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

It looks like a good start. The main thing I would look at is if you really need your tasks list. Right now it gives you 2 things: a way to find out about failures, and a way to count active tasks. You should be able to set up exception failures for each task, and you might not need to limit the number of active tasks, depending on your specific concerns.

Your comment in WaitMessageFromNetwork indicates that it won't block the thread, but that doesn't seem possible with your current setup. You can make the read asynchronous, but that would require changes to the Run loop. This highlights another reason to change from the list-centric error reporting - you only find out about errors when a new command comes in from the network.

\$\endgroup\$
1
  • \$\begingroup\$ You're right, for the non-blocking part I need to test with the real library. For error reporting It will be better to use ContinueWith(..,TaskContinuationOptions.OnlyOnFaulted). \$\endgroup\$
    – PlageMan
    Aug 21, 2015 at 17:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.