views:

344

answers:

1

Hello everyone, I'm starting to implement a simple daemon that basically fetches a file from an FTP location with the help of a BackgroundWorker component to kind of guarantee sort of thread safety there. Although I sort of feel I'm heading towards the right direction, I'm not completely familiar with the technologies involved, therefore not quite at ease with the whole application life cycle, say there's scenarios that I definitely don't know how to handle yet, namely, what to do with the stop event, what would happen if the service gets stopped while the worker is running, et cétera. I guess the following piece of code barely represents what I'm trying to achieve:

#region Daemon Events
protected override void OnStart(string[] args)
{
 this.transferBackgroundWorker.RunWorkerAsync();
}

protected override void OnStop()
{
            this.transferBackgroundWorker.CancelAsync(); // Thanks Wolfwyrd!
}
#endregion

#region BackgroundWorker Events
    private void transferBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        worker.WorkerSupportsCancellation = true;

        #region FTP Download

        FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(Daemon.FTP_HOST);
        ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;
        ftpRequest.Credentials = new NetworkCredential(Daemon.FTP_USER, Daemon.FTP_PASS);
        FtpWebResponse ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();

        using(Stream ftpResponseStream = ftpResponse.GetResponseStream())
        {
            using (StreamWriter sw = File.CreateText(FILE_NAME))
            {
                sw.WriteLine(ftpResponseStream);
                sw.Close();
            }
            ftpResponse.Close();
        }
        #endregion

        e.Result = "DEBUG: Download complete" + ftpResponse.StatusDescription;
    }

private void transferBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
 if (e.Error != null)
 {
  EventLog.WriteEntry("Exception caught: " + e.Error.Message);
 }
 else
 {
  EventLog.WriteEntry(e.Result.ToString());
 }
}
#endregion

Any and every suggestion would be really appreciated. Thanks much in advance for the assistance.

Edit: just reimplemented the FTP file fetching.

+2  A: 

A BackgroundWorker works by spinning off some task onto a separate background thread independent from the calling application. The lifecycle of the background worker follows the pattern of:

  • Create the Background Worker (i.e. BackgroundWorker bgw = new ...)
  • Call bgw.RunWorkerAsync A seperate background thread is started by the BackgroundWorker and the DoWork method is invoked on this thread
  • Optional notification events are raised, these are raised by calling bgw.ReportProgress() and are subscribed to (and acted upon) in the BackgroundWorkers parent thread
  • Optional cancellation may occur, this happens when bgw.CancelAsync() is called. This call may come from any thread with access to the BackgroundWorker object. It is up to you to honour the cancellation by checking on the CancellationPending property in your DoWork method
  • The BackgroundWorker DoWork method finishes either through natural completion or due to abnormal termination (i.e. an unhandled exception occurs in the DoWork method) The - RunWorkerCompleted method is invoked

To offer advice on your original question, it seems that what you want to do is flag the backgroundworker as supporting cancellation (bgw.WorkerSupportsCancellation = true) and have your stop event call the CancelAsync() method on the BackgroundWorker. Update your DoWork method to poll the "CancellationPending" property at suitable junctures and perform the cancellation when it occurs leaving your service in a known state.

Wolfwyrd
Wolfwyrd, I want to thank you a lot for such an elaborated explanation! It's been definitely informative.
Nano Taboada
I've added the this.transferBackgroundWorker.WorkerSupportsCancellation = true; and the proper cancellation call at the OnStop method, which I'm still not quite understanding is where/how should I check for the CancellationPending property.
Nano Taboada
The check should happen at each discrete step in DoWork, every time you're about to do the next step in your task, check if you should cancel instead. So for example, if your FTP call works by getting a series of chunks of the file, in your method you would use (pseudo style): while (BlocksLeft) { if (!CancellationPending) GetNextChunk(); else AbortAndCleanup(); }. The idea is you're checking just before you do the next step in your DoWork process and handling the cancellation gracefully
Wolfwyrd
And if it's helped, vote it up :)
Wolfwyrd