none
Issue with invoking tasks on a background thread and using Parallel.Foreach

    Question

  • Hello,

    Hello,

    I've following C# windows service that instantiates MyProductsManager class as follows:

     public static void Main(string[] args)
            {
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] 

    new MyWindowsServiceHost() 
    };
                ServiceBase.Run(ServicesToRun);
            }

     public class MyWindowsServiceHost: ServiceBase
        {
    public ServiceHost m_serviceHost = null;
            public MyWindowsServiceHost()
            {

            }

            protected override void OnStart(string[] args)
            {
                try
                {
                    if (m_serviceHost != null)
                        m_serviceHost.Close();
                    m_serviceHost = new ServiceHost(typeof(MyProductsManager));
                    m_serviceHost.Open();

                }
    }

    public class MyProductsManager
    {

    MyProductsManager()
    {
        SendStartServiceEmailAlert();---for informing users that service has started
        GetProductsInfo();
    }
    ....
    ....
    ....
    }
    GetProductsInfo() needs to be invoked on a background thread so that the windows service(from within Service control Manager) starts up immediately instead of waiting for GetProductsInfo() method to complete.

    GetProductsInfo() needs to carry out three operations:
    Step # 1.Call GetProductList() to get a list of all products
    Step # 2.Get data for each of the 400 products in parallel(may be using Parallel.ForEach??) by calling GetProductDetails().
    3.SendTaskCompletionEmailAlert() --This alert is for informing users saying "details for all 400 products have been fetched".This method needs to be invoked only after step #1 and #2 have been completed. 

    How do I achieve this please?


    Thanks.

    --


    • Edited by ANi2000 Thursday, April 12, 2018 1:51 AM
    Wednesday, April 19, 2017 9:15 PM

All replies

  • None of this code belongs in the constructor for your service. A windows service can be constructed but never started. All work for a service should be done in the OnStart method. All your work has to be done on a secondary worker thread so tasks aren't the correct solution here. You should create a regular worker thread in your OnStart method and then have it do all the work.

    As for the ordering of your calls I'm not sure why you're using a windows service as a service runs forever. So your worker thread will be doing what after it has notified the users? Based upon your description all the code you gave (which will be in a worker thread) can be run sequentially. There is no reason to spawn any additional tasks (other than perhaps the parallel fetching). So I could envision this:

    public class MyWindowsService : ServiceBase
    {
       protected override void OnStart ( string[] args )
       {
          //Start worker thread
          _thread = new Thread(DoWork);
          _thread.IsBackground = true;
          _thread.Start();
       }
    
       protected override void OnStop ()
       {
          _terminate.Set();
    
          //Wait for worker to terminate
          //SCM will kill it if it takes too long
          _thread.Wait();
       }
    
       private void DoWork ( )
       {
          //Get data (could use Task here with cancellation token so you can terminate when service terminates)
          var data = ...;
    
          //Start parallel work
          ...
    
          //Block until work complete or termination request
          SendEmail();
       
          //Now what, your thread should not terminate???
       }
    
       private Thread _thread;
       private ManualResetEvent _terminate = new ManualResetEvent(false);
    }

    The event is so the OnStop method can tell the worker thread to terminate. The thread should not terminate before that otherwise your service is basically dead. If you don't need to run this code in a loop or anything then you probably shouldn't be using a service at all.

    Michael Taylor
    http://www.michaeltaylorp3.net

    Thursday, April 20, 2017 12:30 AM
    Moderator
  • Thanks very much Michael.Appreciate your response.You're right, actually I missed out on some points when drafting my question.Have updated it now.Would you be able to respond to that please?

    Thanks again.

    Thursday, April 20, 2017 3:39 PM
  • I'm not sure what your ServiceHost type is doing so I cannot answer to that but this code does need to be running in a worker thread so move that code to the DoWork method that I mentioned in my post. The remainder of your code would then be running in a separate thread and therefore (other than using Parallel to speed up the processing) can be done synchronously like normal.

    void DoWork ()
    {
       //All this is running on a background thread so you 
       //can call it in the order you want it to run
       SendStartServiceEmailAlert();---for informing users that service has started
    
        GetProductsInfo();
    
        //Do your Parallel.ForEach stuff here
        var results = Parallel.ForEach(...);
    
        //Wait for results to complete
        while (!results.IsCompleted) 
        {
           //Check for termination request
           //Sleep for a little bit
        };
    
        //All work done so send your next email
    }
    You're still going to have to ensure your thread doesn't terminate though. Does this really need to be running constantly or can you simply create a console app that is scheduled to run via Task Scheduler? If your runs are one offs then a service is not the correct choice.

    Thursday, April 20, 2017 4:01 PM
    Moderator