9

I have a button on my windows form that calls the RunWorkerAsync() method, this in turn performs an action which then updates a ListBox on the same form.

After the DoWork event has finished I assign the Result for the event (Which is a list), I process the RunWorkerCompleted() event and then perform the following code to update my Listbox

alt text

which calls this:

alt text

(Apologies, code formatting won't work)

Now when I run the application and press the refresh button the following exception appears:

alt text

How would I get around this?

Edit:

The exception is thrown on the folowing statement, this occurs in the DoWork method where I clear the contents to keep the list up to date;

listBoxServers.Items.Clear();

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Jamie Keeling
  • 9,806
  • 17
  • 65
  • 102

6 Answers6

13

You may not call Invoke on the list box, but on the form. For WinForms applications I use something like:

...
this.Invoke((MethodInvoker)delegate()
{
    // Do stuff on ANY control on the form.
});
...

Depending on the .NET version, you may have to declare a delegate for MethodInvoker yourself as

public delegate void MethodInvoker();

However, you might also consider using the ReportProgress feature of the Background Worker. The respective event handler should be called in the context of the form's thread.

Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
11

Here's a snippet which I find very handy:

public static void ThreadSafe(Action action)
{
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, 
        new MethodInvoker(action));
}

You can pass it any delegate of Action type or simply a lambda like this:

ThreadSafe(() =>
{
    [your code here]
});

or

ThreadSafe(listBoxServers.Items.Clear);
Nobody
  • 4,731
  • 7
  • 36
  • 65
  • 1
    Isn't this WPF? Does WinForms support the Dispatcher? – Greg D Dec 13 '10 at 13:46
  • `Dispatcher` is a WPF class but this code will work with WinForms. – Nobody Dec 13 '10 at 13:52
  • For some reason Dispatcher isn't recognised by IntelliSense, I have the System.Threading statement included. – Jamie Keeling Dec 13 '10 at 14:01
  • 3
    Make sure you have `WindowsBase.dll` referenced in your project. The namespace is `System.Windows.Threading`. http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx – Nobody Dec 13 '10 at 14:03
  • Winforms has a mechanism already for handling this. There is no reason to add a reference to yet another DLL and then the overhead of creating a `Dispatcher` object. Just use the `Control.Invoke()` or `Control.BeginInvoke()` method, as recommended by the [_correct_ answer that was posted](http://stackoverflow.com/a/4429346) – Peter Duniho Nov 07 '16 at 17:09
9

What I've done is something like this every time you need to run something across threads:

listBoxServers.BeginInvoke(
    (Action)
    (() => listBoxServers.Items.Clear()));
Chris Conway
  • 16,269
  • 23
  • 96
  • 113
1

Background threads are not allowed to update the UI in Windows applications, so you have to revert the control back to the UI thread for the actual update.

Create a method that will call UpdateServerDetails on the main thread, like this:

private void DispatchServerDetails(List<ServerDetails> details)
{
  Action<List<ServerDetails>> action = UpdateServerDetails;
  Dispatcher.Invoke(action)
}

and then call DispatchServerDetails instead of UpdateServerDetails.

Some caveats:
-This works best in WPF applications, for WinForms, you'll need to jump through some hoops, or you can use InvokeRequired
-The UI update is still synchronous, so if UpdateServerDetails does a lot of work, it will block the UI thread (not your case, just to be on the safe side).

SWeko
  • 30,434
  • 10
  • 71
  • 106
  • @Jamie http://stackoverflow.com/questions/975087/wpf-dispatcher-and-running-it-in-background – jan Dec 13 '10 at 12:37
1

Using Invoke in a windows forms project can be a little tricky, there're some pitfalls that are documented but easy to miss. I recommend using something like you'll find in this question:

Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?

It handles cases where invoke is not required, is called from different threads, handle is or isn't created, etcetcetc. It could be easily modified to be SafeInvoke() and SafeBeginInvoke() if you're not a fan of the bool parameter.

(Included here for your convenience:

/// Usage:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);

// or 
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);


/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of 
/// updater.  False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread.  If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {
        if (!uiElement.IsHandleCreated)
        {
            // Do nothing if the handle isn't created already.  The user's responsible
            // for ensuring that the handle they give us exists.
            return;
        }

        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}
Community
  • 1
  • 1
Greg D
  • 43,259
  • 14
  • 84
  • 117
0

I just figured out a simpler way without using Invoke:

int fakepercentage = -1;
//some loop here......if no loop exists, just change the value to something else
if (fakepercentage == -1)
{
    fakepercentage = -2;
}
else
{
    fakepercentage = -1;
}
backgroundworker1.ReportProgress(fakepercentage);

Then in backgroundworker1_ProgressChanged(object sender, ProgressChangedEventArgs e):

if (e.ProgressPercentage < 0)
{
    //access your ui control safely here
}