多线程过程的参数和返回值(C# 和 Visual Basic)

在多线程应用程序中提供和返回值是很复杂的,因为必须将对某个过程的引用传递给线程类的构造函数,该过程不带参数也不返回值。 下面几节介绍一些提供参数和从不同线程上的过程返回值的简单方法。

为多线程过程提供参数

为多线程方法调用提供参数的最好办法是将目标方法包裹在类中,并为该类定义字段,这些字段将被用作新线程的参数。 这种方法的优点是,任何时候想要启动新线程,都可以创建类的新实例,该实例带有自身的参数。 例如,假设您有一个计算三角形面积的函数,如以下代码所示:

Function CalcArea(ByVal Base As Double, ByVal Height As Double) As Double
    CalcArea = 0.5 * Base * Height
End Function
double CalcArea(double Base, double Height)
{
    return 0.5 * Base * Height;
}

可以编写一个包含 CalcArea 函数的类,并创建一些字段来存储输入参数,如以下所示:

Class AreaClass
    Public Base As Double 
    Public Height As Double 
    Public Area As Double 
    Sub CalcArea()
        Area = 0.5 * Base * Height
        MessageBox.Show("The area is: " & Area.ToString)
    End Sub 
End Class
class AreaClass
{
    public double Base;
    public double Height;
    public double Area;
    public void CalcArea()
    {
        Area = 0.5 * Base * Height;
        MessageBox.Show("The area is: " + Area.ToString());
    }
}

要使用 AreaClass,可以创建一个 AreaClass 对象并设置 Base 和 Height 属性,如以下代码所示:

Protected Sub TestArea()
    Dim AreaObject As New AreaClass
    Dim Thread As New System.Threading.Thread(
                        AddressOf AreaObject.CalcArea)
    AreaObject.Base = 30
    AreaObject.Height = 40
    Thread.Start()
End Sub
protected void TestArea()
{
    AreaClass AreaObject = new AreaClass();

    System.Threading.Thread Thread =
        new System.Threading.Thread(AreaObject.CalcArea);
    AreaObject.Base = 30;
    AreaObject.Height = 40;
    Thread.Start();
}

注意,调用 CalcArea 方法后,TestArea 过程并不检查 Area 字段的值。 因为 CalcArea 运行于单独的线程,因此,如果在调用 Thread.Start 后立即检查,则无法确定 Area 字段已被设置。 下一节讨论自多线程过程返回值的更好方法。

从多线程过程返回值

由于这些过程不能为函数也不能使用 ByRef 参数,因而从运行于不同线程的过程返回值是很复杂的。 返回值的最简单方法是:使用 BackgroundWorker 组件来管理线程,在任务完成时引发事件,然后用事件处理程序处理结果。

下面的示例通过从运行于单独线程的某过程引发一个事件来返回值:

Private Class AreaClass2
    Public Base As Double 
    Public Height As Double 
    Function CalcArea() As Double 
        ' Calculate the area of a triangle. 
        Return 0.5 * Base * Height
    End Function 
End Class 

Private WithEvents BackgroundWorker1 As New System.ComponentModel.BackgroundWorker

Private Sub TestArea2()
    Dim AreaObject2 As New AreaClass2
    AreaObject2.Base = 30
    AreaObject2.Height = 40

    ' Start the asynchronous operation.
    BackgroundWorker1.RunWorkerAsync(AreaObject2)
End Sub 

' This method runs on the background thread when it starts. 
Private Sub BackgroundWorker1_DoWork(
    ByVal sender As Object, 
    ByVal e As System.ComponentModel.DoWorkEventArgs
    ) Handles BackgroundWorker1.DoWork

    Dim AreaObject2 As AreaClass2 = CType(e.Argument, AreaClass2)
    ' Return the value through the Result property.
    e.Result = AreaObject2.CalcArea()
End Sub 

' This method runs on the main thread when the background thread finishes. 
Private Sub BackgroundWorker1_RunWorkerCompleted(
    ByVal sender As Object,
    ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs
    ) Handles BackgroundWorker1.RunWorkerCompleted

    ' Access the result through the Result property. 
    Dim Area As Double = CDbl(e.Result)
    MessageBox.Show("The area is: " & Area.ToString)
End Sub
class AreaClass2
{
    public double Base;
    public double Height;
    public double CalcArea()
    {
        // Calculate the area of a triangle. 
        return 0.5 * Base * Height;
    }
}

private System.ComponentModel.BackgroundWorker BackgroundWorker1
    = new System.ComponentModel.BackgroundWorker();

private void TestArea2()
{
    InitializeBackgroundWorker();

    AreaClass2 AreaObject2 = new AreaClass2();
    AreaObject2.Base = 30;
    AreaObject2.Height = 40;

    // Start the asynchronous operation.
    BackgroundWorker1.RunWorkerAsync(AreaObject2);
}

private void InitializeBackgroundWorker()
{
    // Attach event handlers to the BackgroundWorker object.
    BackgroundWorker1.DoWork +=
        new System.ComponentModel.DoWorkEventHandler(BackgroundWorker1_DoWork);
    BackgroundWorker1.RunWorkerCompleted +=
        new System.ComponentModel.RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted);
}

private void BackgroundWorker1_DoWork(
    object sender,
    System.ComponentModel.DoWorkEventArgs e)
{
    AreaClass2 AreaObject2 = (AreaClass2)e.Argument;
    // Return the value through the Result property.
    e.Result = AreaObject2.CalcArea();
}

private void BackgroundWorker1_RunWorkerCompleted(
    object sender,
    System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    // Access the result through the Result property. 
    double Area = (double)e.Result;
    MessageBox.Show("The area is: " + Area.ToString());
}

可以通过使用 QueueUserWorkItem 方法的可选 ByVal 状态对象变量为线程池线程提供参数和返回值。 线程计时器线程也支持将状态对象用于此目的。 有关线程池和线程计时器的信息,请参见 线程池(C# 和 Visual Basic)线程计时器(C# 和 Visual Basic)

请参见

任务

演练:利用 BackgroundWorker 组件进行多线程处理(C# 和 Visual Basic)

参考

线程同步(C# 和 Visual Basic)

事件(C# 编程指南)

委托(C# 编程指南)

概念

线程池(C# 和 Visual Basic)

多线程应用程序(C# 和 Visual Basic)

其他资源

事件 (Visual Basic)

委托 (Visual Basic)

组件中的多线程处理