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.

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

5 Comments

You're right Matt, Windows Service doesn't have synchronization context so ConfigureAwait can't help. Your approach should work.
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, 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.
@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, 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. stackoverflow.com/questions/125964/…

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.