2

I have this somewhat convoluted situation. I'm given an object o, a string n and array of objects args. I am to call the method n on the object o with the given arguments.

The problem is that this input always comes to the same thread, and the objects provided are (sometimes) controls created on different threads. So when I try to invoke certain methods on them I get the cross-thread invocation exception.

It wouldn't be a problem if I could just check if the object is a control and then call it's Invoke method and do my stuff inside of that call. Unfortunately, I can't do that because I could for example be passed a ControlCollection object that belongs to some control. If I call it's Add method, I get the exception, but the ControlCollection has no Invoke method.

What can I do about this? Performance isn't something I'm worried about yet so any solution is a good one. Maybe if I could catch the exception and get the thread it want's me to call from, then repeat the invocation? Or maybe there's some way to get the thread an object "belongs" to?

Luka Horvat
  • 4,283
  • 3
  • 30
  • 48
  • `the objects provided are (sometimes) controls created on different threads` - just to be 100% clear - you are creating controls not only in UI thread, but in other threads too? – anikiforov May 12 '15 at 21:11
  • @anikiforov Hmmm... Now that you mention it, I am, but that's probably not what I want to do, huh? Can I create multiple forms on the same thread and have them run concurrently? Because at the moment I'm making a new thread per each form. – Luka Horvat May 12 '15 at 21:13
  • maybe. What I was getting at is - if all your controls were created on a single (UI) thread - you might get away by checking `InvokeRequired` and then calling 'Invoke' on some known `Control` instance - created with the sole reason of checking `InvokeRequired`. If you could get a reference to that instance in your method somehow, that is. – anikiforov May 12 '15 at 21:17
  • 2
    @LukaHorvat if you have forms on multiple threads you need to do have a [message pump](http://stackoverflow.com/questions/1566791/run-multiple-ui-threads) on each thread for them to work properly – m0sa May 12 '15 at 21:19
  • @m0sa I don't think multiple threads are a requirement. So far I thought they were necessary for multiple forms to be responsive, but if that's not the case I'll gladly do something better. My application is a console application so when I "just" make a form (no new thread) and call Show on it, it's shows up frozen. On the other hand if I call ShowDialog it works, but blocks. – Luka Horvat May 12 '15 at 21:21
  • 2
    it's frozen on Show() because there's no message pump in the thread – m0sa May 12 '15 at 21:23
  • 1
    I see. Is there some default UI thread available for console apps that I can somehow create my threads in? What are my options? – Luka Horvat May 12 '15 at 21:25
  • 1
    no, you have to create one yourself (by calling `Application.Run` in a STA thread) – m0sa May 12 '15 at 21:33
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/77658/discussion-between-m0sa-and-luka-horvat). – m0sa May 12 '15 at 21:40
  • 1
    Read this: http://www.codeproject.com/Articles/566196/Using-SynchronizationContext-in-Windows-Forms – Ron Beyer May 13 '15 at 02:27
  • @RonBeyer: Yes, that's the way to go. But there must only be one GUI thread! The OP would have to change that. Also, they could invoke on the main form - the actual control is not needed. – JeffRSon May 13 '15 at 08:00

1 Answers1

3

Could you do it this way?

Action action = () => o.GetType().GetMethod(n).Invoke(o, args);

if (o is Control)
{
    var c = o as Control;
    c.Invoke(action);
}
else if (o is ControlCollection)
{
    var c = (o as ControlCollection).Owner;
    c.Invoke(action);
}
else
{
    action();
}

One approach to generalize this would be to do it this way:

        Action action = () => o.GetType().GetMethod(n).Invoke(o, args);

        var controlMaps = new Func<object, Control>[]
        {
            x => x as Control,
            x => o is ControlCollection ? (o as ControlCollection).Owner : null,
        };

        var c = controlMaps
                .Select(m => m(o))
                .Where(x => x != null)
                .FirstOrDefault();

        if (c != null)
        {
            c.Invoke(action);
        }
        else
        {
            action();
        }

Then, if you have a bunch of different object type you can create a mapping in the controlMaps array.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • @LukaHorvat - It wouldn't be too onerous to generalize this. – Enigmativity May 13 '15 at 11:50
  • It would be if you wanted to map the whole System.Windows.Forms namespace. – Luka Horvat May 13 '15 at 15:24
  • @LukaHorvat - I don't think it would be too bad. The total number of types would be fairly limited - especially since all types derived from `Control` are already covered and that's a large proportion of the namespace. – Enigmativity May 13 '15 at 23:14