委托将值返回给回调

时间:2014-02-06 16:45:43

标签: c# asp.net multithreading winforms

我遇到以下问题非常困难,尽管我觉得很难解决: 我有一个应用程序,当您单击按钮时调用代理。它看起来像这样:

    private void button1_Click(object sender, EventArgs e)
    {
        MyTaskWorkerDelegate worker = new MyTaskWorkerDelegate(returnANumber);
        AsyncCallback completedCallback = new AsyncCallback(DelegateMethod);

        object[] myArray = new object[1];
        myArray[0] = "The number is: ";

        worker.BeginInvoke(completedCallback,myArray);
    }

方法“returnANumber”应该睡一秒钟然后返回数字100。 它看起来像这样:

    private int returnANumber()
    {
        Thread.Sleep(1000);
        return 100;
    }

当睡眠结束并且返回的数字是棘手的部分。在click函数中创建的数组将传递给回调方法。此方法应显示MessageBox,显示数组的字符串和委托返回的数字。该函数如下所示:

    private void DelegateMethod(IAsyncResult arr)
    {
        object[] myArray = (object[])arr.AsyncState;

        //Messagebox should show "The number is: 100"
        MessageBox.Show(myArray[0].ToString() + theNumberFromTheMethos);
    }

我无法弄清楚如何访问委托方法返回的值... 请帮帮我:(

1 个答案:

答案 0 :(得分:1)

您可以通过MyTaskWorkerDelegate.EndInvoke访问返回值。因此,您必须保留对MyTaskWorkerDelegate的引用并将其传递给DelegateMethod。您的代码目前的样子,您需要一个单独的对象来通过myArray传递MyTaskWorkerDelegateIAsyncResult.AsyncState

如果使用匿名lambda / delegate并访问局部变量,则可以避免这种情况并大大简化代码。 C#编译器将保持状态:

private void button1_Click(object sender, EventArgs e)
{
    MyTaskWorkerDelegate worker = new MyTaskWorkerDelegate(returnANumber);

    object[] myArray = new object[1];
    myArray[0] = "The number is: ";

    AsyncCallback completedCallback = new AsyncCallback((ar) => 
    {
        var result = worker.EndInvoke(ar);

        // you cannot use MessageBox here, you're on a non-UI random pool thread
        Debug.Print(myArray[0].ToString() + result);
    });

    worker.BeginInvoke(completedCallback, null); // no need to pass the state
}

另一点是,您无法使用MessageBox委托中的AsyncCallback,而是在调用回调时使用随机池线程。您需要使用Control.BeginInvoke

AsyncCallback completedCallback = new AsyncCallback((ar) => 
{
    var result = worker.EndInvoke(ar);

    // you cannot use MessageBox here, you're on a non-UI random pool thread
    this.BeginInvoke(new MethodInvoker(() =>
    {
        // here you can use MessageBox, you're on the UI thread
        MessageBox.Show(myArray[0].ToString() + result);
    }));
});

请注意,Control.BeginInvoke的目的与Delegate.BeginInvoke完全不同,尽管名称相似。它用于在WinForms UI线程上异步调用回调。

也就是说,如果您可以使用.NET 4.5(或。),那么您可以使用TPLasync/await 更多地简化您的代码事件,正如@SLaks所指出的那样。 NET 4.0 + Microsoft.Bcl.Async和VS2012 +)。您的代码可能就像这样简单:

private int returnANumber()
{
    Thread.Sleep(1000);
    return 100;
}

private async void button1_Click(object sender, EventArgs e)
{
    object[] myArray = new object[1];
    myArray[0] = "The number is: ";

    int result = await Task.Run(() => returnANumber());
    MessageBox.Show(myArray[0].ToString() + result);
}

此外,如果Thread.Sleep(1000)的唯一原因是暂停,您可以改为使用Task.Delay

private async Task<int> returnANumber()
{
    await Task.Delay(1000);
    return 100;
}

private async void button1_Click(object sender, EventArgs e)
{
    object[] myArray = new object[1];
    myArray[0] = "The number is: ";

    int result = await returnANumber();
    MessageBox.Show(myArray[0].ToString() + result);
}

这样,您根本不会显式使用池线程。