Search code examples
c#wpfbackgroundworker

C# WPF Exiting application with running background workers which update dialog


My question is similar to this one, I have pretty much the same code setup except I'm using BackgroundWorker instead of WorkflowRuntime. (And the answer doesn't appear to work for me)

In the past I have used Application.Current.Shutdown(); in the closing event of MainWindow, however I was hoping that by properly disposing of this window which I've made a static resource I could perhaps not need that.

The problem is that if I exit via closing MainWindow after all the background tasks terminate an empty BackgroundDialog remains open.

public partial class BackgroundDialog : Window
{
    private static BackgroundDialog _Dialog = new BackgroundDialog();
    private static UIElementCollection TasksView { get { return _Dialog.BackgroundList.Children; } }

    public static void Add(BackgroundItem item)
    {
        if (TasksView.Count == 0)
        {
            _Dialog.Show();
        }
        TasksView.Add(item);
    }

    public static void Remove(BackgroundItem item)
    {
        TasksView.Remove(item);
        if (TasksView.Count == 0)
        {
            if (_Release)
            {
                FinishRelease();
            }
            else
            {
                _Dialog.Hide();
            }
        }
    }
    private static bool _Release = false;
    private static void FinishRelease()
    {
        // FinishRelease may be called from a BackgroundWorker thread finishing
        // This results in _Dialog.Close() not behaving as expected
        // For more details: https://stackoverflow.com/questions/5659930/wpf-window-not-closing
        _Dialog.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
        {
            _Dialog.Close();
            _Dialog = null;
        }));
    }

    public static void Release(EventArgs e)
    {
        _Release = true;
        if (TasksView.Count == 0)
        {
            FinishRelease();
        }
        else foreach (BackgroundItem Task in TasksView)
        {
            Task.Abort();
        }
    }
}

public partial class BackgroundItem : UserControl
{
    public delegate void TaskHandler(BackgroundWorker Worker);

    public interface IBackgroundTask
    {
        bool IsIndeterminate { get; }
        int MaxProgress { get; }
        string Title { get; }
        string Description(int progress);
        TaskHandler Exec { get; }
    }

    private BackgroundWorker Worker;

    public BackgroundItem(IBackgroundTask task)
    {
        InitializeComponent();

        Title.Text = task.Title;
        Description.Text = task.Description(0);

        Progress.Value = 0;
        Progress.Minimum = 0;
        Progress.Maximum = task.MaxProgress;
        Progress.IsIndeterminate = task.IsIndeterminate;

        BackgroundDialog.Add(this);

        Worker = new BackgroundWorker()
        {
            WorkerReportsProgress = true,
            WorkerSupportsCancellation = true,
        };
        Worker.DoWork += (object sender, DoWorkEventArgs e) =>
        {
            task.Exec?.Invoke(Worker);
        };
        Worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) =>
        {
            BackgroundDialog.Remove(this);
        };
        Worker.ProgressChanged += (object sender, ProgressChangedEventArgs e) =>
        {
            Progress.Value = e.ProgressPercentage;
            Description.Text = task.Description(e.ProgressPercentage);
        };
        Worker.RunWorkerAsync();

        Stop.Click += (object sender, RoutedEventArgs e) =>
        {
            Abort();
        };
    }

    public void Abort()
    {
        Worker.CancelAsync();
        Stop.IsEnabled = false;
        StopText.Text = "Stopping";
    }
}

public partial class MainWindow : Window
{
    private class MyTask : BackgroundItem.IBackgroundTask
    {
        public bool IsIndeterminate => true;
        public int MaxProgress => 100;
        public string Title => "I'm Counting";
        public BackgroundItem.TaskHandler Exec => (BackgroundWorker Worker) =>
        {
            for (int i = 0; i < 100; ++i)
            {
                if (Worker.CancellationPending)
                {
                    break;
                }
                Worker.ReportProgress(i);
                Thread.Sleep(500);
            }
        };

        public string Description(int progress)
        {
            return progress.ToString();
        }
    }

    public MainWindow()
    {
        InitializeComponent();

        Loaded += (object sender, RoutedEventArgs e) => {
            new BackgroundItem(new MyTask());
            new BackgroundItem(new MyTask());
            new BackgroundItem(new MyTask());
        };
    }

    protected override void OnClosed(System.EventArgs e)
    {
        base.OnClosed(e);
        BackgroundDialog.Release(e);
    }
}

Solution

  • I feel silly, must have been the end of the day on Friday....here was the problem

    in BackgroundDialog:

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;
    }
    

    Must have been a relic from before I found this solution. However, some cancellation is needed to prevent the user from closing the dialog from the taskbar. So I wrapped the cancel with the statement if (!_Release)