Опубликован: 02.08.2013 | Доступ: свободный | Студентов: 464 / 16 | Длительность: 18:38:00
Специальности: Программист
Самостоятельная работа 14:

Многопоточное программирование

Аннотация: Данная лабораторная работа посвящена асинхронному выполнению ресурсоёмких задач.

Цель работы: освоить технологию работы с классом BackgroundWorker

Выполнение ресурсоёмких задач и пользовательский интерфейс

Создавая простое приложение для Windows Phone, не выполняющее сложных вычислений, не обращающееся к веб-сервисам, не выполняющее работу с файлами, о выполнении каких-либо задач вне потока пользовательского интерфейса обычно не задумываются. Если мы, например, в обработчике события нажатия на кнопку захотим выполнить какое-то простое вычисление, или проверку какого-либо условия, вычислительная нагрузка от таких операций ляжет на поток пользовательского интерфейса. До тех пор, пока эта нагрузка незначительна, подобное не сказывается на скорости отклика приложения. Но если нужно выполнить какую-либо ресурсоёмкую операцию, это можно сделать и без дополнительных усилий по использованию для неё другого потока, однако, выполнение её в потоке пользовательского интерфейса приведет к заметным задержкам. Как вы увидите в примере, который мы разберем в этой лабораторной работе, интерфейс может быть блокирован. То есть, приложение не реагирует на взаимодействие пользователя с элементами управления, анимация может либо приостановиться, либо работать рывками.

В предыдущих лабораторных работах мы сталкивались с вызовом асинхронных методов для выполнения задач, которые могут занимать потенциально достаточно большое время. При объявлении метода, в котором мы вызывали асинхронные операции, используется модификатор async, операция вызывается с использованием оператора await. Это приводит к тому, что выполнение метода приостанавливается до тех пор, пока не будет получен ответ от вызываемой процедуры. Например – данные, загруженные с веб-сервера. При этом использование async и await упрощает написание асинхронного кода, так как до появления этого механизма (это произошло в Visual Studio 2012) при вызове асинхронного метода нужно было предусматривать использование функции, которая вызывается при завершении работы метода и обрабатывает полученные результаты. Такой подход применим и сейчас, но он усложняет структуру кода, в то время как асинхронный код, написанный с использованием async и await, хотя и решает задачи асинхронной обработки, выглядит как синхронный.

До сих пор мы вызывали асинхронные методы каких-либо объектов, но нередко возникают задачи, для успешного решения которых нужно асинхронно выполнить произвольный код. То есть, выполнить этот код не в потоке пользовательского интерфейса, а в другом потоке. При таком подходе интерфейс продолжает реагировать на воздействия пользователя. Решить подобную задачу можно, например, с использованием класса BackgroundWorker (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker%28v=vs.95%29.aspx).

Он предоставляет средства для выполнения операций в потоках, отличных от потока пользовательского интерфейса. Рассмотрим пример, иллюстрирующий работу с BackgroundWorker.

Работа с BackgroundWorker

В основу этого примера положен пример "Work with ProgressIndicator and hide", http://code.msdn.microsoft.com/Work-with-ProgressIndicator-545eb39d. Здесь представлен русифицированный код, снабжённый дополнительными комментариями и дополнениями, иллюстрирующими недостатки синхронного выполнения задач, аналогичных тем, которые решаются с помощью выполнения задач в потоке, отличном от потока пользовательского интерфейса. В частности, здесь к исходному коду, добавлена демонстрация того, как выполнение ресурсоёмкой задачи в потоке пользовательского интерфейса блокирует анимацию, делает невозможной нормальную работу с элементами управления пользовательского интерфейса. Рекомендуется при освоении этого примера загрузить его по указанной ссылке, так как в него могут быть внесены дополнения и обновления, а код, приведенный здесь, использовать как справочное руководство.

В исходном примере так же показана работа с индикатором выполнения, который сигнализирует пользователю о том, что в настоящий момент выполняется какая-либо ресурсоёмкая задача. В подобных задачах такой подход весьма актуален, так как пользователю нужно сообщать о том, что приложение выполняет какую-то ресурсоёмкую задачу и пользователю, для выполнения результатов, следует некоторое время подождать. При этом весьма желательно указывать время, которое может занять выполнение задачи. В нашем случае мы искусственно создаём 5-секундную задержку, которая имитирует сложную вычислительную задачу. В реальных условиях, если неизвестно точно, сколько времени будет выполняться операция, экспериментальным путём можно выяснить хотя бы приблизительное время и сообщить об этом пользователю.

На рис. 22.1. вы можете видеть окно проекта.

Окно проекта

увеличить изображение
Рис. 43.1. Окно проекта

На главной странице (Листинг 43.1) присутствуют кнопки Выполнить асинхронно и Выполнить синхронно, запускающие выполнение имитации ресурсоёмкой задачи, на выполнение которой нужно 5 секунд, соответственно, асинхронно, в дополнительном потоке, и синхронно, в потоке пользовательского интерфейса. Прямоугольник на странице анимирован – для демонстрации того, как синхронное выполнение ресурсоёмких задач влияет на пользовательский интерфейс. Флаг Проверьте отклик интерфейса позволяет проверить реакцию интерфейса в ходе синхронного и асинхронного выполнения задачи.

<phone:PhoneApplicationPage
    x:Class="CSWP8ProgressIndicator.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
    <phone:PhoneApplicationPage.Resources>
        <Storyboard x:Name="Storyboard1">
            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).
(SolidColorBrush.Color)" Storyboard.TargetName="rectangle">
                <EasingColorKeyFrame KeyTime="0" Value="#FF1A1AEA"/>
                <EasingColorKeyFrame KeyTime="0:0:5" Value="#FFEA1A1A"/>
            </ColorAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).
(CompositeTransform.Rotation)" Storyboard.TargetName="rectangle">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:5" Value="359.926"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </phone:PhoneApplicationPage.Resources>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" 
Margin="12,17,0,28">
            <TextBlock Text="АСИНХРОННЫЙ КОД" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>

            <TextBlock Text="главная" Margin="9,-7,0,0" 
Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBlock x:Name="tbMessage" 
Text="Нажмите на кнопку "Выполнить асинхронно" для начала выполнения фонового процесса. 
Вы увидите идикатор выполнения. Когда задача будет завершена. индикатор будет скрыт,
будет выведено сообщение о завершении работы. 
Нажмите на кнопку "Выполнить синхронно" для демонстрации синхронного выполнения 
ресурсоёмкой задачи." TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Visible"/>
            <Button x:Name="btnWork" Content="Выполнить асинхронно" HorizontalAlignment="Left" Margin="83,294,0,0" 
VerticalAlignment="Top" Click="btnWork_Click"/>
            <Rectangle x:Name="rectangle" Fill="#FFF4F4F5" 
HorizontalAlignment="Left" Height="100" Margin="173,477,0,0" 
Stroke="Black" VerticalAlignment="Top" Width="100" 
RenderTransformOrigin="0.5,0.5">
                <Rectangle.RenderTransform>
                    <CompositeTransform/>
                </Rectangle.RenderTransform>
            </Rectangle>
            <Button x:Name="btnSyncWork" Content="Выполнить синхронно" 
HorizontalAlignment="Left" Margin="83,374,0,0" VerticalAlignment="Top" 
Click="btnSyncWork_Click" Width="305"/>
            <CheckBox Content="Проверьте отклик интерфейса" 
HorizontalAlignment="Left" Margin="10,222,0,0" 
VerticalAlignment="Top"/>
        </Grid>
      </Grid>
</phone:PhoneApplicationPage>
Листинг 43.1. Код страницы MainPage.xaml