How to get return value when BeginInvoke/Invoke is called in C#
Asked Answered
B

7

53

I've this little method which is supposed to be thread safe. Everything works till i want it to have return value instead of void. How do i get the return value when BeginInvoke is called?

public static string readControlText(Control varControl) {
        if (varControl.InvokeRequired) {
            varControl.BeginInvoke(new MethodInvoker(() => readControlText(varControl)));
        } else {
            string varText = varControl.Text;
             return varText;
        }

    }

Edit: I guess having BeginInvoke is not nessecary in this case as i need value from GUI before the thread can continue. So using Invoke is good as well. Just no clue how to use it in following example to return value.

private delegate string ControlTextRead(Control varControl);
    public static string readControlText(Control varControl) {
        if (varControl.InvokeRequired) {
            varControl.Invoke(new ControlTextRead(readControlText), new object[] {varControl});
        } else {
            string varText = varControl.Text;
             return varText;
        }

    }

But not sure how to get value using that code either ;)

Baillie answered 6/2, 2010 at 17:44 Comment(1)
If you need to work with the value returned from an invoke, that must be because you need a "continuation passing style" pattern. Which can be alleviated with async, await and Task.Exhilarant
K
84

You have to Invoke() so you can wait for the function to return and obtain its return value. You'll also need another delegate type. This ought to work:

public static string readControlText(Control varControl) {
  if (varControl.InvokeRequired) {
    return (string)varControl.Invoke(
      new Func<String>(() => readControlText(varControl))
    );
  }
  else {
    string varText = varControl.Text;
    return varText;
  }
}
Kizzykjersti answered 6/2, 2010 at 18:9 Comment(0)
B
20

EndInvoke may be used to get a return value from a BeginInvoke call. For example:

    public static void Main() 
    {
        // The asynchronous method puts the thread id here.
        int threadId;

        // Create an instance of the test class.
        AsyncDemo ad = new AsyncDemo();

        // Create the delegate.
        AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

        // Initiate the asychronous call.
        IAsyncResult result = caller.BeginInvoke(3000, 
            out threadId, null, null);

        Thread.Sleep(0);
        Console.WriteLine("Main thread {0} does some work.",
            Thread.CurrentThread.ManagedThreadId);

        // Call EndInvoke to wait for the asynchronous call to complete,
        // and to retrieve the results.
        string returnValue = caller.EndInvoke(out threadId, result);

        Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".",
            threadId, returnValue);
    }
}
Brushwork answered 6/2, 2010 at 18:23 Comment(1)
Overall good example, although in my case nobugz solution is perfect and exactly what was required.Baillie
W
4
public static string readControlText(Control varControl)
{
    if (varControl.InvokeRequired)
    {
        string res = "";
        var action = new Action<Control>(c => res = c.Text);
        varControl.Invoke(action, varControl);
        return res;
    }
    string varText = varControl.Text;
    return varText;
}
Wreath answered 6/2, 2010 at 18:19 Comment(2)
Looks similar to nobugz solution but his looks cleaner to me :-)Baillie
Yep, I agree. When I posted my solution, I don't see nobugz solution yet:)Wreath
T
1

If you want a return value from you method, you shouldn't be using async version of the method, you should use .Invoke(...). Which is synchronous, that is it will execute your delegate, and won't return until it's complete. In your example as it is now, BeginInvoke will send the request to execute your delegate, and return right away. So there is nothing to return.

Trojan answered 6/2, 2010 at 17:48 Comment(1)
It can be Invoke or BeginInvoke as long as i get back value. All i need is to read value of combobox or textbox from other thread.Baillie
P
1

Is something like this what you wanted?

// begin execution asynchronously
IAsyncResult result = myObject.BeginInvoke("data.dat", null, null);

// wait for it to complete
while (result.IsCompleted == false) {
   // do some work
   Thread.Sleep(10);
   }

// get the return value
int returnValue = myObject.EndInvoke(result);
Preen answered 6/2, 2010 at 17:48 Comment(1)
Not quite. I guess the use of BeginInvoke is not nessecary since i just want to read value of ComboBox or TextBox and that value is important to execute other commands. So i could use it in Invoke. Just not sure how i could use your example in my case where the main purpose of it is to call method from another thread, read gui value and get back to me so program can go further.Baillie
A
0
delegate string StringInvoker();
    string GetControlText()
    {
        if (control.InvokeRequired)
        {
            string controltext = (string)control.Invoke(new StringInvoker(GetControlText));
            return(controltext);
        }
        else
        {
            return(control.Text);
        }
    }

//simple & elegant but it is needed to wait for another thread to execute delegate; however if you cannot proceed without the results...

Anonym answered 21/1, 2014 at 11:48 Comment(0)
B
0

Here is more cleaner solution

    public delegate void InvokeIfRequiredDelegate<T>(T obj) where T : ISynchronizeInvoke;
    public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action) where T : ISynchronizeInvoke
    {
        if (obj.InvokeRequired)
            obj.Invoke(action, new object[] { obj });
        else
            action(obj);
    }

    public static string GetThreadSafeText(this Control control)
    {
        string value = string.Empty;
        control.InvokeIfRequired((c) => value = c.Text);
        return value;
    }
    public static void SetThreadSafeText(this Control control, string value)
    {
        control.InvokeIfRequired((c) => c.Text = value);
    }

Usage:

    public string Name
    {
        get => txtName.GetThreadSafeText();
        set => txtName.SetThreadSafeText(value);
    }
Barbra answered 10/9, 2021 at 6:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.