Question

I'm working on an app that grabs and installs a bunch of updates off an an external server, and need some help with threading. The user follows this process:

  • Clicks button
  • Method checks for updates, count is returned.
  • If greater than 0, then ask the user if they want to install using MessageBox.Show().
  • If yes, it runs through a loop and call BeginInvoke() on the run() method of each update to run it in the background.
  • My update class has some events that are used to update a progress bar etc.

The progress bar updates are fine, but the MessageBox is not fully cleared from the screen because the update loop starts right after the user clicks yes (see screenshot below).

  • What should I do to make the messagebox disappear instantly before the update loop starts?
  • Should I be using Threads instead of BeginInvoke()?
  • Should I be doing the initial update check on a separate thread and calling MessageBox.Show() from that thread?

Code

// Button clicked event handler code...
DialogResult dlgRes = MessageBox.Show(
    string.Format("There are {0} updates available.\n\nInstall these now?", 
    um2.Updates.Count), "Updates Available", 
    MessageBoxButtons.YesNo, 
    MessageBoxIcon.Question, 
    MessageBoxDefaultButton.Button2
);

if (dlgRes == DialogResult.Yes)
{
    ProcessAllUpdates(um2); 
}

// Processes a bunch of items in a loop
private void ProcessAllUpdates(UpdateManager2 um2)
{
    for (int i = 0; i < um2.Updates.Count; i++)
    {
        Update2 update = um2.Updates[i];

        ProcessSingleUpdate(update);

        int percentComplete = Utilities.CalculatePercentCompleted(i, um2.Updates.Count);

        UpdateOverallProgress(percentComplete);
    }
}

// Process a single update with IAsyncResult
private void ProcessSingleUpdate(Update2 update)
{
    update.Action.OnStart += Action_OnStart;
    update.Action.OnProgress += Action_OnProgress;
    update.Action.OnCompletion += Action_OnCompletion;

    //synchronous
    //update.Action.Run();

    // async
    IAsyncResult ar = this.BeginInvoke((MethodInvoker)delegate() { update.Action.Run(); });
}

Screenshot

Windows Mobile Bug

Was it helpful?

Solution

Your UI isn't updating because all the work is happening in the user interface thread. Your call to:

this.BeginInvoke((MethodInvoker)delegate() {update.Action.Run(); }) 

is saying invoke update.Action.Run() on the thread that created "this" (your form), which is the user interface thread.

Application.DoEvents()

will indeed give the UI thread the chance to redraw the screen, but I'd be tempted to create new delegate, and call BeginInvoke on that.

This will execute the update.Action.Run() function on a seperate thread allocated from the thread pool. You can then keep checking the IAsyncResult until the update is complete, querying the update object for its progress after every check (because you can't have the other thread update the progress bar/UI), then calling Application.DoEvents().

You also are supposed to call EndInvoke() afterwards otherwise you may end up leaking resources

I would also be tempted to put a cancel button on the progress dialog, and add a timeout, otherwise if the update gets stuck (or takes too long) then your application will have locked up forever.

OTHER TIPS

Have you tried putting a

Application.DoEvents()

in here

if (dlgRes == DialogResult.Yes)
{
   Application.DoEvents(); 
   ProcessAllUpdates(um2); 
}

@ John Sibly

You can get away with not calling EndInvoke when dealing with WinForms without any negative consequences.

The only documented exception to the rule that I'm aware of is in Windows Forms, where you are officially allowed to call Control.BeginInvoke without bothering to call Control.EndInvoke.

However in all other cases when dealing with the Begin/End Async pattern you should assume it will leak, as you stated.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top