如何在C#中调用BeginInvoke/Invoke时获取返回值

52

我有一个小方法,这个方法应该是线程安全的。一切都运行良好,直到我想让它返回一个值而不是 void 时。当调用 BeginInvoke 时,我如何获取返回值?

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

    }

编辑:我猜在这种情况下拥有BeginInvoke并不是必要的,因为我需要从GUI获取值,然后线程才能继续。所以使用Invoke也很好。只是不知道如何在以下示例中使用它来返回值。

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;
        }

    }

但我也不确定如何使用那段代码来获取值 ;)


如果您需要使用从调用返回的值进行工作,那么这必须是因为您需要一个“传递样式”模式。这可以通过asyncawaitTask来缓解。 - v.oddou
7个回答

83

您需要调用Invoke()方法,这样您才能等待函数返回并获取其返回值。您还需要另一个委托类型。以下代码应该可行:

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;
  }
}

20

EndInvoke可用于从BeginInvoke调用中获取返回值。例如:

    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);
    }
}

总体来说,这是一个很好的例子,虽然在我的情况下,nobugz的解决方案是完美的,也正是所需的。 - MadBoy

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;
}

看起来与nobugz的解决方案相似,但他的对我来说更加简洁 :-) - MadBoy
是的,我同意。当我发布我的解决方案时,我还没有看到nobugz的解决方案:) - Sergey Teplyakov

2

你是想要这样的东西吗?

这个内容和it技术无关,只是普通的问句。
// 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);

不完全正确。 我猜使用BeginInvoke并非必要,因为我只想读取ComboBox或TextBox的值,并且该值对于执行其他命令很重要。 因此,我可以在Invoke中使用它。 只是不确定如何在我的情况下使用您的示例,其中其主要目的是从另一个线程调用方法,读取GUI值并将结果返回给我,以便程序可以继续执行。 - MadBoy

2

如果你想从方法中获得返回值,那么你不应该使用异步版本的方法,而应该使用同步的.Invoke(...)方法。这意味着它会执行你的委托,并且在完成后才会返回。在你现在的例子中,BeginInvoke将发送请求以执行你的委托,然后立即返回。因此没有什么可以返回。


只要我能够得到返回值,它可以是Invoke或BeginInvoke。我所需要的就是从其他线程读取组合框或文本框的值。 - MadBoy

0

这里有一个更加简洁的解决方案

    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);
    }

使用方法:

    public string Name
    {
        get => txtName.GetThreadSafeText();
        set => txtName.SetThreadSafeText(value);
    }

0
delegate string StringInvoker();
    string GetControlText()
    {
        if (control.InvokeRequired)
        {
            string controltext = (string)control.Invoke(new StringInvoker(GetControlText));
            return(controltext);
        }
        else
        {
            return(control.Text);
        }
    }

//简单而优雅,但需要等待另一个线程执行委托;但是如果没有结果,您将无法继续...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接