views:

63

answers:

2

I got a .net WinForms application. I have a UserControl which gets instantiated based on user action - upon instantiation, it performs some time-consuming tasks on a background thread (using BackgroundWorker), while displaying the ajaxy spinning animation. The user can click away at anytime, then click back onto the user control (which would start the background thread all over again).

When the user clicks away, I want to dispose of the UserControl and all the resources that it holds (including the background thread). What is the best way of doing this?

+1  A: 

Call the CancelAsync method on the BackgroundWorker and wait for it to terminate. Construct your worker code so that it frequently checks for the cancel flag.

If there are no negative side effects if the thread continues to run for a while, and it will in no way reference the User Control or any resource held by it, you can dispose of the User Control after requesting the thread to terminate.

EDIT: Demo code

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class FrmMain : Form
    {
        public FrmMain()
        {
            InitializeComponent();
        }

        private void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("BG Done");
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            bg.WorkerSupportsCancellation = true;
            bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
            bg.DoWork += new DoWorkEventHandler(bg_DoWork);
            bg.RunWorkerAsync();
        }

        void bg_DoWork(object sender, DoWorkEventArgs e)
        {
            int i=0;

            while (!bg.CancellationPending)
            {
                lblStatus.BeginInvoke((MethodInvoker) delegate { lblStatus.Text = i + " sec."; });
                System.Threading.Thread.Sleep(1000);
                i++;
            }

            lblStatus.BeginInvoke((MethodInvoker)delegate { lblStatus.Text = "CANCEL"; });
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            bg.CancelAsync();
            while (bg.IsBusy) // For real code limit max wait time in while loop
            {
                System.Threading.Thread.Sleep(50);
                Application.DoEvents();
            }
            this.Close();
        }
    }
}


namespace WindowsFormsApplication1
{
    partial class FrmMain
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.bg = new System.ComponentModel.BackgroundWorker();
            this.btnStart = new System.Windows.Forms.Button();
            this.btnStop = new System.Windows.Forms.Button();
            this.lblStatus = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // bg
            // 
            this.bg.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.bg_RunWorkerCompleted);
            // 
            // btnStart
            // 
            this.btnStart.Location = new System.Drawing.Point(39, 13);
            this.btnStart.Name = "btnStart";
            this.btnStart.Size = new System.Drawing.Size(75, 23);
            this.btnStart.TabIndex = 0;
            this.btnStart.Text = "Start";
            this.btnStart.UseVisualStyleBackColor = true;
            this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
            // 
            // btnStop
            // 
            this.btnStop.Location = new System.Drawing.Point(39, 42);
            this.btnStop.Name = "btnStop";
            this.btnStop.Size = new System.Drawing.Size(75, 23);
            this.btnStop.TabIndex = 1;
            this.btnStop.Text = "Stop";
            this.btnStop.UseVisualStyleBackColor = true;
            this.btnStop.Click += new System.EventHandler(this.btnStop_Click);
            // 
            // lblStatus
            // 
            this.lblStatus.AutoSize = true;
            this.lblStatus.Location = new System.Drawing.Point(39, 72);
            this.lblStatus.Name = "lblStatus";
            this.lblStatus.Size = new System.Drawing.Size(73, 13);
            this.lblStatus.TabIndex = 2;
            this.lblStatus.Text = "(Not Running)";
            // 
            // FrmMain
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(423, 136);
            this.Controls.Add(this.lblStatus);
            this.Controls.Add(this.btnStop);
            this.Controls.Add(this.btnStart);
            this.Name = "FrmMain";
            this.Text = "Main";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.ComponentModel.BackgroundWorker bg;
        private System.Windows.Forms.Button btnStart;
        private System.Windows.Forms.Button btnStop;
        private System.Windows.Forms.Label lblStatus;
    }
}
Eric J.
Don't wait for it to terminate, that will deadlock when the BGW has a RunWorkerCompleted event handler.
Hans Passant
Not true, but you need to call Application.DoEvents() while waiting. Added code that demonstrates (VS 2010 / .NET 4)
Eric J.
+1  A: 

You may want to hook into the Progress Changed Event. Have your worker invoke this event whenever it can safely be stopped without losing work. Now with this you can store the current state of the task by setting the value in ProgressChangedEventArgs and then you can restart where you left off without losing work.

ChaosPandion
Can't really do that because the UserControl hits a faraway service that is basically blocking until it returns a result.
AngryHacker