[.net] Understanding Invoke

Started by
6 comments, last by ferr 15 years, 10 months ago
I was creating a C# program that has a function which populates ListView/ListBox/etc objects with data, sometimes a large amount of data. I noticed that the amount of data (i.e. number of records) being populated was large enough to require its own thread.. so I gave it its own thread. Only problem was that within this new thread I'm updating the listview object which was created on the UI Thread.. and, at least at the debugging level, C# doesn't like that (cross-threading error). So I read that if you want to perform a task like that you'd need to use the Invoke method on the control that's on another thread. I did that, but doing it was very strange and I'm not 100% sure about what exactly is going on. Here's a snippet of what some of it looks like:

public delegate void UI_Invoker();
private void lvZones_SelectedIndexChanged(object sender, EventArgs e)
{
    try
    {
        if (lvZones.SelectedIndices.Count > 0)
        {
            lbRecords.Items.Clear();

            if (_binaryZones != null)
            {
                System.Threading.Thread t = new System.Threading.Thread(delegate()
                {
                    foreach (Record record in _binaryZones[lvZones.SelectedIndices[0]].recordList)
                    {
                        this.Invoke(new UI_Invoker(delegate()
                        {
                            lbRecords.Items.Add("Record: " + record._dataList[0]);
                        }));
                    }
                });

                t.Start();
            }
        }
    }
    catch (Exception msg)
    {
        MessageBox.Show(msg.ToString());
    }
}


The use of UI_Invoker as an argument in Invoke seems like a superfluous step, I'm sure there's a good reason people would want to do that, I just don't have a need for it (I would think there would at least be an override for an anonymous function, but no). I also didn't notice until later that I was 'using' the lvZone listview object within the new thread, yet it wasn't bugging.. does it only care about certain interactions? (edit: actually it was bugging, I just didn't notice!) [Edited by - ferr on May 27, 2008 3:13:03 PM]
Advertisement
Control.Invoke is necessary when you modify controls from threads other than their owner/parent thread because the controls themselves are not even close to threadsafe.

Behind the scenes it works like this: Control.Invoke sticks a message containing a reference to your delegate to the control's thread's message queue. When that thread picks up the message it calls the delegate, executing it in its own thread. This guarantees serial access and prevents threading issues.

As for the new Whatever(delegate() { ... }) syntax, that's a limitation of C#. Control.Invoke accepts a Delegate, and you can't turn anonymous delegates into those. To avoid creating your own delegate type, however, you can use the built-in type MethodInvoker.
Ra
Quote:Original post by Ra
To avoid creating your own delegate type, however, you can use the built-in type MethodInvoker.


That's nice to know, thanks. I decided to question the way I used Invoke based on the fact that I needed to create a strange work-around to get it to work right, I would think that if I were doing it the way it was meant to be done then MS would have added an Invoke override that accepts an anonymous delegate. Another area of my code has sporadic interaction with objects on the UI thread, and creating a delegate method for each of them (1-2 lines of code every 30 or so lines) would be terrible looking, so an anonymous delegate seemed to be the most obvious route..
I just noticed that since invoke goes back to execute on the control's thread, if that thread happens to be the UI thread and it's a large execution (such as adding a large amount of items to say a ListView), it will totally ruin the purpose of using multi-threading for performance/responsiveness (in my case). Should I just skip using invoke on large UI processes (unless I'm debugging).. what's the harm? Removing invoke = very responsive UI.
Removing the Invoke may cause your program to break in subtle and hard to debug ways. You may only update Windows.Forms controls from the thread that contains the message loop.

What I do in this case is break the updates down into smaller chunks (call Invoke more often with less data). This keeps things moving nice and smooth.

[OpenTK: C# OpenGL 4.4, OpenGL ES 3.0 and OpenAL 1.1. Now with Linux/KMS support!]

Quote:Original post by ferr
I just noticed that since invoke goes back to execute on the control's thread, if that thread happens to be the UI thread and it's a large execution (such as adding a large amount of items to say a ListView), it will totally ruin the purpose of using multi-threading for performance/responsiveness (in my case). Should I just skip using invoke on large UI processes (unless I'm debugging).. what's the harm? Removing invoke = very responsive UI.


If you don't use invoke, it'll break whether or not you're debugging. You can't do anything multithreaded in relation to WinForms/WPF or any other Windows-based GUI stuff.

So it sounds like you might as well ditch your extra thread completely (if its only responsibility was to update a ListView, which it isn't allowed to do)

You'll have to think of different ways to make your UI responsive.
If you have a huge amount of data, you might want to consider using the ListView in virtual mode instead of pushing all the data into it.
http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.virtualmode.aspx
Quote:Original post by kanato
If you have a huge amount of data, you might want to consider using the ListView in virtual mode instead of pushing all the data into it.
http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.virtualmode.aspx


Yes, I tried that out a bit ago.. it worked fine for certain cases, but it is just so strange at times. It seemed to want to call its retrieval callback at the oddest times, and it was screwing things up. What I ended up doing was just creating a list of ListViewItems (or an array of them, don't recall) and just adding them to the ListView using the AddRange method.


A new problem has popped up, and I believe it's related enough to this thread that I don't need to create a new topic.

I've got an Invoke inside of a worker thread, my user 'cancels' the action which fires a thread.Abort() and closes the form once aborted, however it RANDOMLY crashes and complains about the Invoke call still trying to go through, and that it cannot access the disposed objects. Try/Catch doesn't seem to help avoid this error, it's a crash-to-desktop type of thing. What can be done about this?

within the form closing event:
if (core_threads != null){    core_threads.ForEach(delegate(System.Threading.Thread t)     {        if (t.ThreadState == System.Threading.ThreadState.Running)        {            t.Abort();            while (t.IsAlive) { } //I could probably do t.Join() instead        }    });}


core_threads is a List<t> of Threads, each of which are instantiated in a main worker thread which exists to work away from the UI thread. When the form is closed, the main worker thread must be aborted, as well as any open core threads that do the main processing. The problem is that the UI thread has been closed before the core_thread, and that core_thread, while it is in the process of being aborted, is still running and at times may try to invoke the (closed) UI thread.

the invoked method within the thread:
Invoke(new MethodInvoker(delegate(){    if (lvZoneInfo.Items.SubItems[1].Text != record._signature)     {        lvZoneInfo.Items.SubItems[1].Text = record._signature;        lvZoneInfo.Items.SubItems[2].Text = (int.Parse(lvZoneInfo.Items.SubItems[2].Text) + 1).ToString();    }}));


Using Invoke to update something on the UI thread lends to racing issues, that if statement bounces off race errors.

Here's an article that asks pretty much the same question.

I'm pretty much doing the major things that the posters comment about (like aborting the threads within the closing event), but that doesn't seem to be cutting it..

The error, note 'AccessControls_Zone' is the name of the form:
Cannot access a disposed object.Object name: 'AccessControls_Zone'.


[Edited by - ferr on June 23, 2008 2:54:09 PM]

This topic is closed to new replies.

Advertisement