Parameters and Return Values for Multithreaded Procedures (C# and Visual Basic)

Supplying and returning values in a multithreaded application is complicated because the constructor for the thread class must be passed a reference to a procedure that takes no arguments and returns no value. The following sections show some simple ways to supply parameters and return values from procedures on separate threads.

Supplying Parameters for Multithreaded Procedures

The best way to supply parameters for a multithreaded method call is to wrap the target method in a class and define fields for that class that will serve as parameters for the new thread. The advantage of this approach is that you can create a new instance of the class, with its own parameters, every time you want to start a new thread. For example, suppose you have a function that calculates the area of a triangle, as in the following code:

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

You can write a class that wraps the CalcArea function and creates fields to store input parameters, as follows:

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

To use the AreaClass, you can create an AreaClass object, and set the Base and Height properties as shown in the following code:

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

Notice that the TestArea procedure does not check the value of the Area field after calling the CalcArea method. Because CalcArea runs on a separate thread, the Area field is not guaranteed to be set if you check it immediately after calling Thread.Start. The next section discusses a better way to return values from multithreaded procedures.

Returning Values from Multithreaded Procedures

Returning values from procedures that run on separate threads is complicated by the fact that the procedures cannot be functions and cannot use ByRef arguments. The easiest way to return values is to use the BackgroundWorker component to manage your threads and raise an event when the task is done, and process the results with an event handler.

The following example returns a value by raising an event from a procedure running on a separate thread:

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

You can provide parameters and return values to thread-pool threads by using the optional ByVal state-object variable of the QueueUserWorkItem method. Thread-timer threads also support a state object for this purpose. For information on thread pooling and thread timers, see Thread Pooling (C# and Visual Basic) and Thread Timers (C# and Visual Basic).

See Also

Tasks

Walkthrough: Multithreading with the BackgroundWorker Component (C# and Visual Basic)

Reference

Thread Synchronization (C# and Visual Basic)

Events (C# Programming Guide)

Delegates (C# Programming Guide)

Concepts

Thread Pooling (C# and Visual Basic)

Multithreaded Applications (C# and Visual Basic)

Other Resources

Events (Visual Basic)

Delegates (Visual Basic)

Multithreading in Components