1

I have created a Service which runs on a Client. This Service inserts a Log Entry in a Database on Server. The Log entry is inserted via API which is running on Server. Below is the simplified code inside Service1 class. The Service is Timer based and runs repeatedly when needed. Hence, it needs to insert the Log Entry when needed. i.e. SendToServer() function is executed repeatedly. I have remove the Timer code because it is not relevant here.

public class Logs
{
 public string param1 { get; set; }
 public string param2 { get; set; }
}

static HttpClient client = new HttpClient();
System.Timers.Timer timer = new System.Timers.Timer(); //New Edit

protected override void OnStart(string[] args)
{
 SendToServer();
 timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);//New Edit
 timer.Interval = 60000; //New Edit
 timer.Enabled = true; //NewEdit
}

private void OnElapsedTime(object source, ElapsedEventArgs e)//New Edit
{
  SendToServer();
}

public void SendToServer()
{
 RunAsync().GetAwaiter().GetResult();
}

static async Task RunAsync(EventLogEntry Log)
{
 client.BaseAddress = new Uri("https://<IP>:<Port>/");
 client.DefaultRequestHeaders.Accept.Clear();
 client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

 Logs log = new Logs
 {
  param1 = "value1";
  param2 = "value2";
 };
 var url = await CreateLogsAsync(log);
}

static async Task<Uri> CreateLogsAsync(Logs log)
{
 ServicePointManager.ServerCertificateValidationCallback = delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
 {
  return true;
 };
 HttpResponseMessage response = await client.PostAsync("api/Logs", new StringContent(new JavaScriptSerializer().Serialize(log), Encoding.UTF8, "application/json"));
 response.EnsureSuccessStatusCode();
 return response.Headers.Location;
}

When I install the service or power up the Client Machine. The first database insert works fine. However, the data is not inserted in the database the second time. When I restart the system it again inserts the first log. I sense that there is problem with ASync Functions and the first call to API never returns correctly after inserting the data.

The similar code works perfectly in Console Application.

Faheem
  • 469
  • 1
  • 6
  • 23

1 Answers1

1

The problem is that your Windows Service is shutting down after executing this line:

RunAsync().GetAwaiter().GetResult();

The OnStart() method needs to do something that keeps the service running, or it will shutdown when OnStart() returns. However, you can't put a perpetual loop, like while (true), in the OnStart() method because the system calls OnStart() as a callback to your code, and it expects OnStart() to return in a timely manner. Likewise, starting a Task or initiating anything on the ThreadPool won't work (as you've seen) because these run as background threads, and background threads are stopped automatically when the application stops. From the link:

A thread is either a background thread or a foreground thread. Background threads are identical to foreground threads, except that background threads do not prevent a process from terminating. Once all foreground threads belonging to a process have terminated, the common language runtime ends the process. Any remaining background threads are stopped and do not complete.

To solve the problem, you need to start a foreground thread. Here's a very rough example that should solve the immediate problem. You'll need tweak it to do what you want it to do.

using System.Threading;

public sealed class MyService : ServiceBase
{
    private Thread           _thread;
    private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);

    protected override void OnStart(string[] args)
    {
        // Create the thread object and tell it to execute the Run method
        _thread = new Thread(Run);

        // Name the thread so it is identifyable
        _thread.Name = "MyService Thread";

        // Set the thread to be a foreground thread (keeps the service running)
        _thread.IsBackground = false;

        // Start the thread
        _thread.Start();
    }

    protected override void OnStop()
    {
        // Set the shutdown event to tell the foreground thread to exit
        // the while loop and return out of the Run() method
        _shutdownEvent.Set();

        // Wait for the foreground thread to exit
        _thread.Join();
    }

    private void Run()
    {
        // The while loop keeps the foreground thread active by executing
        // over and over again until Stop() sets the shutdown event, which
        // triggers the while loop to exit.
        while (!_shutdownEvent.WaitOne(1000))
        {
            // Put your logic here.
            RunAsync().GetAwaiter().GetResult();
        }
    }
}

Note that the while loop in the Run() method will run over and over again until the shutdown event is set (see the Stop() method). The argument to the WaitOne() call is in milliseconds. As coded, the WaitOne() call will block for 1 second before executing your code again. You'll need to tweak this so that your code runs when you want it to.

HTH

Matt Davis
  • 43,149
  • 15
  • 89
  • 118
  • You're right Matt, Windows Service doesn't have synchronization context so `ConfigureAwait` can't help. Your approach should work. – Arman Ebrahimpour May 14 '20 at 21:26
  • Matt, I have incorporated your suggestions in my code. But the problem is not solved. It works similarly for the first time as before but the second time the service is not even initiated now. I have checked and this is because OnStop() Method is not executed. This might be because I am using Timer() Class to repeatedly execute OnElapsedTime() method after 1 minute. I have edited the code and marked with //New Edit Comment. Can you suggest to put Service Shutdown statements anywhere else? – Faheem May 16 '20 at 08:26
  • @Faheem, unfortunately, you're running into the same problem as before. `SendToServer` runs a task on the thread pool (background thread), and the timer executes the `Elapsed` event also on the thread pool (background thread). You have nothing in your approach that creates a foreground thread which will keep the service running after `OnStart()` returns. Try implementing the example I provided, but change `1000` to `60000` for the call to `WaitOne()` since you only want your task to run every minute. – Matt Davis May 16 '20 at 09:33
  • @MattDavis, No Difference in the behavior after increasing the time. Just that the data is shown in the Database on Server after 1 Min due to added Wait Time. – Faheem May 16 '20 at 15:22
  • @Faheem, are you running my example, or still making changes to yours? The updated code won't keep the service running because it doesn't create any foreground threads in the `OnStart()` method. If you ***are*** running my example, then there is likely an exception occurring the second time the database update is tried. You can debug the service to see what's going on. https://stackoverflow.com/questions/125964/easier-way-to-debug-a-windows-service – Matt Davis May 16 '20 at 21:27