Nano Taboada
Nano Taboada

Reputation: 4182

Implementing a thread-safe FTP file fetching daemon on Windows

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.

Upvotes: 0

Views: 1641

Answers (1)

Wolfwyrd
Wolfwyrd

Reputation: 15916

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.

Upvotes: 2

Related Questions