views:

113

answers:

2

I'm creating menu items in a separate thread and adding them to the menu created in the main thread. I'm using Invoke for that. Getting "Value does not fall within the expected range" exception.

            //creating new thread
            Thread thread = new Thread(LoadRecentTasks);
            thread.IsBackground = true;
            thread.Start();

    private void LoadRecentTasks()
    {
        EntryCollection recentEntries = Entry.GetRecentEntries(10);
        foreach (Entry entry in recentEntries)
        {
            MenuItemPlus menuItem = new MenuItemPlus();
            menuItem.Text = entry.GetShortDescription(28);
            menuItem.Click += recentTasksMenuItem_Click;
            menuItem.Tag = entry;
            AddRecentMenuItem(menuItem);
        }
    }

    private void AddRecentMenuItem(MenuItemPlus menuItem)
    {
        if (InvokeRequired)
        {
            BeginInvoke(new CallbackDelegate<MenuItemPlus>(AddRecentMenuItem), menuItem);
        }
        else
        {
            menuItemRecent.MenuItems.Add(menuItem); //<-- exception thrown here
        }
    }

    delegate void CallbackDelegate<T>(T t);

Any suggestions?

UPDATE: i've tried it with Invoke too - same result.

menuItemRecent is created as part of the form's initialization routine. The thread is started on form's Activated event

A: 

I assume this happens when control is not fully created, causing InvokeRequired to give you a false reading.

      /// <summary>
  /// It's possible for InvokeRequired to return false when running in background thread.
  /// This happens when unmanaged control handle has not yet been created (need to throw).
  /// This can also happen when control is created on background thread (need to debug.assert).
  /// </summary>
  /// <param name="control">Control to check against.</param>
  public bool InvokeRequired(Control control)
  {
     return InvokeRequired(control, false);
  }

  /// <param name="backgroundControl">If set to true, will not assert when control on background thread. </param>
  public bool InvokeRequired(Control control, bool controlOnBackgroundByDesign)
  {
     if (control.InvokeRequired)
        return true;

     if (!control.IsHandleCreated)
        Debug.WriteLine("Control access issue: Underlying control handle has not been created yet.  At this point in time cannot reliably test if invoke is required.");

     if (!controlOnBackgroundByDesign)
     {
        // Check for control on background thread.
        if(!this.IsOnUiThread)
           Debug.WriteLine("Control access issue: It's recommended that all controls reside on a single foreground thread.");
     }

     // At this point, program is executing on control's thread.
     return false;
  }
GregC
Of course, this code sample is incomplete, so I could not possibly repro and debug for ya.
GregC
Thanks, Greg. menuItemRecent is created as part of the form's initialization routine. The thread is started on form's Activated event.
Muxa
Have you had a chance to try the supplied InvokeRequired replacement? Did it work as planned, or did you get some messages in the debug output panel?
GregC
I don't think it is possible for InvokeRequired to give bad values in CF. InvokeRequired on a control in CF is MAD SIMPLE. It just takes the thread id that created it in the Ctor and then compares it to the current thread id in InvokeRequired.
Quibblesome
I've tried it with your code. Couple of issues: IsHandleCreated and IsOnUiThread where not available (i've using .NET CF 2.0)I've tried (control.Handle == IntPtr.Zero) instead of (!control.IsHandleCreated) - it never had that case.I was thinking may be it is possible to create the menu item in the main UI thread, but return the object reference back to my second thread. Pseudocode:LoadRecentTasks(){ foreach (Entry entry in recentEntries) { AddRecentMenuItem(MainUIThread.NewMenuItem()); } }}
Muxa
This is exactly the point. You should always create Control objects on UI thread.
GregC
Granted, I posted desktop dotNET code, but same principles should apply.
GregC
IsOnUIThread checks current thread id to the one that you'd previously save off when your app is just starting, in Application.Run()
GregC
A: 

Hmmmm. Whenever I call BeginInvoke I always do this instead:

BeginInvoke(new CallbackDelegate<MenuItemPlus>(AddRecentMenuItem), new object[]{menuItem});

IIRC correctly I always use the object array because without it in the past I've got weird exceptions.

Quibblesome