Dave Gordon
Dave Gordon

Reputation: 1835

Long running task with dialog

I have this long running process and I want to display a form over the main app form explaining that it is long running with the ubiquitous circle progress bar. I just can't get anything to work properly. The form doesn't show properly or animate the progress circle. If I try a different method as shown then the task doesn't finish!

private void lbMethods_SelectedIndexChanged(object sender, EventArgs e)
    {
        switch (lbMethods.SelectedIndex)
        {
            case (int)Methods.none:
                break;
            case (int)Methods.Susan:
                Log("Starting Susan Edge Detection");
                Progressing("Please wait for edge detection");
                Task t = new Task(() => ProcessSusan());
                while (!t.IsCompleted)
                {
                    Application.DoEvents();
                }
                Log("Detection Finished");
                Progressing("", false);
                break;
            default:
                break;
        }
    }

    private void ProcessSusan()
    {
        Susan s = new Susan(CurrentImage);
        List<IntPoint> corners = s.GetCorners();
    }

    private void Progressing(string message, bool Show = true)
    {

        if (Show)
        {
            lblStatus.Text = message;
            Progress.Style = ProgressBarStyle.Continuous;
        }
        else
        {
            lblStatus.Text = "...";
            Progress.Style = ProgressBarStyle.Blocks;
        }
    }

The long running form code looks like this:

public partial class FrmProcessing : Form
{
    public string description { get; set; }

    public FrmProcessing(string description)
    {
        InitializeComponent();
        lblDescription.Text = description;

    }

    // Let the calling method close this form.
    public void Close()
    {

        this.Close();
    }


}

Upvotes: 2

Views: 1323

Answers (2)

noseratio
noseratio

Reputation: 61736

The Application.DoEvents() is often misused. On top of the common issue with the code re-entrancy via UI, you're also executing a busy-waiting loop.

You didn't specify where exactly you want to display the modal dialog. As I understand it, the code might look like this:

private void lbMethods_SelectedIndexChanged(object sender, EventArgs e)
{
    switch (lbMethods.SelectedIndex)
    {
        case (int)Methods.none:
            break;
        case (int)Methods.Susan:
            Log("Starting Susan Edge Detection");
            Progressing("Please wait for edge detection");

            var dialog = new FrmProcessing();
            dialog.StartTaskFunc = () =>
                Task.Run(ProcessSusan);
            dialog.ShowDialog();

            Log("Detection Finished");
            Progressing("", false);
            break;
        default:
            break;
    }
}

public partial class FrmProcessing : Form
{
    public Func<Task> StartTaskFunc { get; set; }

    public string description { get; set; }

    public FrmProcessing(string description)
    {
        InitializeComponent();
        lblDescription.Text = description;

        // async event handler for "Form.Load"
        this.Load += async (s, e) =>
        {
            // start the task and await it
            await StartTaskFunc();
            // close the modal form when the task finished
            this.Close();
        };
    }
}

This is just a skeletal implementation. You should add some exception handling, cancellation and progress report logic for ProcessSusan. Here is a very good read on how to do that: Enabling Progress and Cancellation in Async APIs.

I've also answered a similar question recently. See if you can use the same approach.

Upvotes: 4

poy
poy

Reputation: 10537

It appears you are not taking advantage of what TPL actually offers.

The following code snippet still blocks the GUI thread:

Task t = new Task(() => ProcessSusan());
while (!t.IsCompleted)
{
  Application.DoEvents();
}

Because it will cycle so rapidly, the results will be undesirable. Replace it with the following:

await Task.Run(()=>ProcessSusan());

This will put the process on a different thread, and then invoke the second half of your method on the GUI thread once ProcessSusan() completes.

NOTE

You must be the async keyword after private in the method definition.

Upvotes: 3

Related Questions