0

Hello, this is my first question, I'll keep the tips on how to make a good question in mind, but if I make a mistake (like making this question too long!), please excuse me.

Now let's talk about my struggles, you can find my current situation and what I'm trying to do toward the end of this post.

I'm making my own dll and I encountered my current problem when I first tried to use one of my threading methods:

public void DoThread(Action method, bool isBG = false)
{
    Thread workerThread =
        new Thread(new ThreadStart(method)) {
            Name = method.Method.Name + "Thread",
            IsBackground = isBG
        };

    workerThread.Start();
}

to pass it a method that updated the WinForm on the MainThread.

Of course, it didn't work.

I asked Google-sensei, and discovered that I had to use Invoke.

So to learn a bit I used the same WinForm app from before to execute this code:

ARThreads customThreads = new ARThreads();
public delegate void DelegateClass();
public DelegateClass delegateMethod;

private void Form1_Load(object sender, EventArgs e)
{
    delegateMethod = new DelegateClass(ComboBox1);    
    customThreads.DoThread(ThreadFunction);
}

private void ComboBox1() => comboBox1.Items.Add("1");

private void ThreadFunction()
{
    MyThreadClass myThreadClassObject = new MyThreadClass(this);
    myThreadClassObject.Run();
}

public class MyThreadClass
{
    Form1 myFormControl1;
    public MyThreadClass(Form1 myForm) => myFormControl1 = myForm;

    public void Run() => myFormControl1.Invoke(myFormControl1.delegateMethod);
}

Taken from a stackoverflow answer (I'm sorry I can't find the source again).

But, as I said in the title, I want my code to use generics, so it wasn't good enough. So I looked at these:

How to pass a Class as parameter for a method? [duplicate]

How to use class name as parameter in C#

C# Pass a Class as a parameter

C# Passing a class type as a parameter

And learnt another new thing, that I had to use Reflection:

How do I use reflection to call a generic method?

How to invoke static generic class methods if the generic type parameters are unknown until runtime?

Dynamically invoking any function by passing function name as string

And so I ended up with this method:

public void DoInvoke<T>(T orignal, string methodName, 
    params object[] parameters) where T : new()
{
    MethodInfo method = typeof(T).GetMethod(methodName);

    if(method != null)
        method.Invoke(orignal, parameters);
}

Where I purposefully omitted to do this:

T instance = new T();
MethodInfo method = typeof(T).GetMethod(methodName);
method.Invoke(instance, parameters);

Because when I used my DoInvoke method like that, even though there where no errors, the ComboBox on the WinForm didn't update.

During my trials with invoking, I discarded the delegate parts, both because this worked:

DoInvoke(this, "ComboBox1", null);

And also because these didn't work as they weren't methods:

DoInvoke(this, "delegateMethod", null);
DoInvoke(this, "DelegateClass", null);

Then, since DoInvoke worked, I started trying to use threads, at first with:

public void ThreadInvoke<T>(T original, string methodName, bool isBG = false, 
    params object[] parameters) where T : new()
{
    Thread workerThread =
        new Thread(() => DoInvoke(original, methodName, parameters)) {
        Name = methodName + "ThreadInvoke",
        IsBackground = isBG
    };

    workerThread.Start();
}

It didn't work, I also decided to just put the two methods together:

public void ThreadInvoke<T>(T original, string methodName, bool isBG = false, 
    params object[] parameters) where T : new()
{
    Type classType = original.GetType();
    MethodInfo method = classType.GetMethod(methodName);

    System.Threading.Thread workerThread =
        new System.Threading.Thread(new System.Threading.ThreadStart (
            delegate () { method.Invoke(original, parameters); })) { 
        Name = methodName + "ThreadInvoke",
        IsBackground = isBG
    };

    workerThread.Start();
}

Mostly thanks to:

Invoke method in new thread (method name from string)?

And I also "made" the following method thanks to a different answer given to the above question:

public void TaskInvoke<T>(T original, string methodName, bool isBG = false, 
    params object[] parameters) where T : new()
{
    MethodInfo method = original.GetType().GetMethod(methodName, 
        BindingFlags.Instance | 
        BindingFlags.Public | 
        BindingFlags.NonPublic
    );

    Task.Factory.StartNew(delegate () { method.Invoke(original, parameters); }, 
        TaskCreationOptions.LongRunning);
}

In case you're wondering, if ThreadInvoke and TaskInvoke had worked, I wouldn't be here.

So what am I exactly trying to do, in a clean and concise way?

In general: 1. Create a new thread; 2. The thread invokes the method it received; 3. The method updates the WinForm, as needed.

Using the WinForm I'm currently testing my dll on as example:

SQLQueries customSQL = new SQLQueries();
ARThreads customThreads = new ARThreads();

private void Form1_Load(object sender, EventArgs e)
{
    //Either this
    customThreads.ThreadInvoke(this, "ComboBoxSetup", parameters: null);
    //Or this
    customThreads.TaskInvoke(this, "ComboBoxSetup", parameters: null);
    //As long as it works
}

public void ComboBoxSetup()
{
    //This is a method to make object arrays
    //out of the result from a sql query
    object[] first = customSQL.ObjectReader(
        "select Column1, Column2 from TestTable order by Column1",
        connectionString,
        2
    );

    if(first != null)        
        comboBox1.Items.AddRange(first);        

    comboBox2.Items.Add("Test");
}

I also saw this:

How do I update the GUI from another thread?

But it's not really what I wanted, as it would turn to this: 1. Create a new thread; 2. The method starts; 3. When needed, each control would call an invoke.

In conclusion, can I thread and invoke a method so that it can update controls of the WinForm/MainThread when and if it needs to? Or do I have to thread the method, and in the method call the invoke for each specificcontrol?

P.S.: I really hope the answer isn't as long-winded as my post lol

  • 2
    Have your class library expose *events*, and then let the *consuming* application do the work if, in one of those event handlers, they want to get back onto the UI thread. That also helps ensure that your library is generally applicable (from Winforms, WPF, Console, ASP.Net, etc, which all have different rules). Also, these days manually creating threads is probably not the best approach to *most* situations. In most situations you can use `Timer`s or callbacks or events from other systems so that *you run code when there's work to do*, not all of the time. – Damien_The_Unbeliever Aug 01 '18 at 10:46
  • Have a look at the [BackgroundWorker-Class](https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx) which raises the ProgressChanged-Event on the Thread it was created on. Or pass a [Dispatcher](https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher(v=vs.110).aspx) which your Thread can Invoke to update the UI. – Hyarus Aug 01 '18 at 11:03
  • @Damien_The_Unbeliever I'll first answer you about last part: thanks, I'll keep it in mind from now on, but in the WinForm I used as an example, it's all about setting up various controls with default data from a sql server, after that there is no other time-consuming operation. I just wanted to speed up the setup of the program and found this problem. – Black Rock Shooter Aug 01 '18 at 12:36
  • As for the first part, I'll see what can be done while also trying out @Hyarus 's advice. – Black Rock Shooter Aug 01 '18 at 12:36
  • 1
    @BlackRockShooter - So, in 2018, the default should be "I'll use async libraries/methods for I/O activity". Rather than "I'll run a thread, that then spends most of it's time blocked waiting for I/O to happen". – Damien_The_Unbeliever Aug 01 '18 at 12:39
  • @Damien_The_Unbeliever I admit I was so focused on threading that I forgot about async completely. – Black Rock Shooter Aug 01 '18 at 13:04

0 Answers0