Поделиться через


Использование объектов среды выполнения Windows в многопоточной среде

В этой статье описывается, как платформа .NET Framework обрабатывает вызовы кода C# и Visual Basic для объектов, предоставляемых средой выполнения Windows или компонентами среды выполнения Windows.

В .NET Framework можно получить доступ к любому объекту из нескольких потоков по умолчанию без специальной обработки. Все, что вам нужно, — это ссылка на объект. В среде выполнения Windows такие объекты называются гибкими. Большинство классов среды выполнения Windows являются гибкими, но некоторые классы не являются, и даже гибкие классы могут потребовать специальной обработки.

По возможности среда CLR обрабатывает объекты из других источников, таких как среда выполнения Windows, как если бы они были объектами .NET Framework:

  • Если объект реализует интерфейс IAgileObject или имеет атрибут MarshalingBehaviorAttribute с MarshalingType.Agile, среда CLR рассматривает его как agile.

  • Если среда CLR может маршалировать вызов из потока, в котором он был сделан, в контекст потоковости целевого объекта, она выполняет это без видимых изменений.

  • Если объект имеет атрибутом MarshalingBehaviorAttribute с MarshalingType.None, класс не предоставляет сведения о маршалинге. CLR не может маршалировать вызов, поэтому выбрасывает исключение InvalidCastException с сообщением о том, что объект можно использовать только в контексте потока, где он был создан.

В следующих разделах описываются последствия этого поведения для объектов из различных источников.

Объекты из компонента среды выполнения Windows, написанного на C# или Visual Basic

Все типы компонентов, которые можно активировать, по умолчанию являются гибкими.

Замечание

Гибкость не подразумевает безопасность потоков. В среде выполнения Windows и .NET Framework большинство классов не являются потокобезопасны, так как безопасность потоков имеет затраты на производительность, и большинство объектов никогда не обращаются к нескольким потокам. Более эффективно синхронизировать доступ к отдельным объектам (или использовать потокобезопасные классы) только по мере необходимости.

При создании компонента среды выполнения Windows можно переопределить значение по умолчанию. См. интерфейс ICustomQueryInterface и интерфейс IAgileObject.

Объекты из среды выполнения Windows

Большинство классов среды выполнения Windows являются гибкими, и среда CLR обрабатывает их как гибкие. В документации по этим классам перечислены атрибуты класса MarshalingBehaviorAttribute(Agile). Однако члены некоторых из этих гибких классов, таких как элементы управления XAML, вызывают исключения, если они не вызываются в потоке пользовательского интерфейса. Например, следующий код пытается использовать фоновый поток для задания свойства кнопки, которая была нажата. Свойство Content кнопки вызывает исключение.

private async void Button_Click_2(object sender, RoutedEventArgs e)
{
    Button b = (Button) sender;
    await Task.Run(() => {
        b.Content += ".";
    });
}
Private Async Sub Button_Click_2(sender As Object, e As RoutedEventArgs)
    Dim b As Button = CType(sender, Button)
    Await Task.Run(Sub()
                       b.Content &= "."
                   End Sub)
End Sub

Вы можете безопасно получить доступ к кнопке с помощью свойства диспетчера или свойства любого объекта, существующего в контексте потока пользовательского интерфейса (например, страницы, на которую находится кнопка). Следующий код использует метод CoreDispatcher объекта RunAsync для отправки вызова в потоке пользовательского интерфейса.

private async void Button_Click_2(object sender, RoutedEventArgs e)
{
    Button b = (Button) sender;
    await b.Dispatcher.RunAsync(
        Windows.UI.Core.CoreDispatcherPriority.Normal,
        () => {
            b.Content += ".";
    });
}

Private Async Sub Button_Click_2(sender As Object, e As RoutedEventArgs)
    Dim b As Button = CType(sender, Button)
    Await b.Dispatcher.RunAsync(
        Windows.UI.Core.CoreDispatcherPriority.Normal,
        Sub()
            b.Content &= "."
        End Sub)
End Sub

Замечание

Свойство Dispatcher не создает исключение при вызове из другого потока.

Время существования объекта среды выполнения Windows, созданного в потоке пользовательского интерфейса, ограничивается временем существования потока. Не пытайтесь получить доступ к объектам в потоке пользовательского интерфейса после закрытия окна.

Если вы создаете собственный элемент управления, наследуя элемент управления XAML или создав набор элементов управления XAML, ваш элемент управления является гибким, так как это объект .NET Framework. Однако если он вызывает члены своего базового класса или составных классов, или при вызове наследуемых элементов эти элементы будут вызывать исключения при вызове из любого потока, кроме потока пользовательского интерфейса.

Классы, которые нельзя маршалировать

Классы среды выполнения Windows, не предоставляющие сведения о маршалинге , имеют атрибут MarshalingBehaviorAttribute с MarshalingType.None. Среди атрибутов, перечисленных в документации такого класса, находится "MarshalingBehaviorAttribute(None)".

Следующий код создает объект CameraCaptureUI в потоке пользовательского интерфейса, а затем пытается задать свойство объекта из потока пула потоков. Среда CLR не может управлять вызовом и вызывает исключение System.InvalidCastException с сообщением о том, что объект можно использовать только в потоковом контексте, в котором он был создан.

Windows.Media.Capture.CameraCaptureUI ccui;

private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    ccui = new Windows.Media.Capture.CameraCaptureUI();

    await Task.Run(() => {
        ccui.PhotoSettings.AllowCropping = true;
    });
}

Private ccui As Windows.Media.Capture.CameraCaptureUI

Private Async Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
    ccui = New Windows.Media.Capture.CameraCaptureUI()

    Await Task.Run(Sub()
                       ccui.PhotoSettings.AllowCropping = True
                   End Sub)
End Sub

Документация по CameraCaptureUI также содержит список "ThreadingAttribute(STA)" среди атрибутов класса, так как он должен быть создан в однопоточном контексте, например в потоке пользовательского интерфейса.

Если вы хотите получить доступ к объекту CameraCaptureUI из другого потока, можно кэшировать объект CoreDispatcher для потока пользовательского интерфейса и использовать его позже для отправки вызова в этом потоке. Или вы можете получить диспетчер из объекта XAML, такого как страница, как показано в следующем коде.

Windows.Media.Capture.CameraCaptureUI ccui;

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    ccui = new Windows.Media.Capture.CameraCaptureUI();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
        () => {
            ccui.PhotoSettings.AllowCropping = true;
        });
}

Dim ccui As Windows.Media.Capture.CameraCaptureUI

Private Async Sub Button_Click_3(sender As Object, e As RoutedEventArgs)

    ccui = New Windows.Media.Capture.CameraCaptureUI()

    Await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
                                Sub()
                                    ccui.PhotoSettings.AllowCropping = True
                                End Sub)
End Sub

Объекты из компонента среды выполнения Windows, написанного на C++

По умолчанию классы в компоненте, который можно активировать, являются гибкими. Однако C++ позволяет значительно контролировать модели потоков и поведение маршалинга. Как описано ранее в этой статье, среда CLR распознает гибкие классы, пытается маршалировать вызовы, когда классы не гибки, и создает исключение System.InvalidCastException, если у класса нет информации о маршалировании.

Для объектов, которые выполняются в потоке пользовательского интерфейса и вызывают исключения при вызове из потока, отличного от потока пользовательского интерфейса, можно использовать объект CoreDispatcher потока пользовательского интерфейса для отправки вызова.

См. также

Руководство C#

руководство по Visual Basic