Параметры и возвращаемые значения для многопоточных процедур (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();
}

Обратите внимание, что процедура TestArea не будет проверять значение поля Area после вызова метода CalcArea. Поскольку CalcArea выполняется в отдельном потоке, не гарантируется, что поле Area будет задано, если проверить его сразу после вызова Thread.Start. В следующем разделе показан более удобный способ получения возвращаемых значений от многопотоковых процедур.

Получение возвращаемых значений от многопоточных процедур

Получение возвращаемых значений от процедур, выполняемых в отдельных потоках, осложняется тем, что процедуры не могут быть функциями и использовать аргументы 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());
}

Передачу параметром и получение возвращаемых значений от потоков из группы потоков можно осуществлять при помощи дополнительной переменной статического объекта ByValметода QueueUserWorkItem. Потоки, запускаемые по таймеру потоков, также поддерживают статические объекты для данной цели. Информация о группировке потоков и потоковых таймеров содержится в разделе Группировка потоков в пул (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)

Многопоточность в компонентах