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

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

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

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

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

  • Если среда 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

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

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