Статьи

31 Дней Манго | День № 23: Модель исполнения

Эта статья является 23-м днем ​​в серии под названием « 31 день манго» и была написана приглашенным автором Самидипом Басу. С Samidip можно связаться в Twitter по адресу @samidip .

Day23-ExecutionModel

Давайте просто согласимся — наши смартфоны очень умные в наши дни. Но вся электронная почта, социальная интеграция, игры и приложения наносят ущерб сравнительно небольшой вычислительной системе и памяти смартфона, но, что более важно, времени автономной работы. Таким образом, для любой мобильной ОС и сторонних приложений очень важно быть очень ответственным за оптимизацию использования ресурсов в смартфоне, чтобы обеспечить максимально плавный, отзывчивый и продолжительный пользовательский опыт. Это где модели исполнения вступают в игру, и становится важным понимать различные состояния / события в течение жизненного цикла приложения. В этой статье мы расскажем о том, как выглядит модель выполнения для Windows Phone и что изменилось в выпуске Mango для потребителей / разработчиков.

Windows Phone Way

Поскольку мы весь день танцуем между различными частями ОС Windows Phone и сторонними приложениями, телефон использует четко определенный подход к управлению жизненным циклом приложений от запуска до завершения. Модель исполнения просто позволяет запускать только одно приложение на переднем планев любое время. Пока мы увидим, насколько это полезно для пользовательского опыта, позвольте мне сыграть в защиту дьявола и аргументировать обратное. Тем не менее, даже если бы это было возможно, можете ли вы представить запуск нескольких приложений на переднем плане в Windows Phone? Разве экранная недвижимость не слишком ограничена, чтобы попытаться это сделать? Сосредоточение внимания на одном приложении на переднем плане просто имеет смысл для форм-фактора телефона и вовлекает пользователя в погружение без хрома / отвлечения. Там, где это удобнее, аналогичные операционные системы Metro UI допускают одновременную стыковку нескольких приложений переднего плана, как в привязанных видах Windows 8. Но я отвлекся J

Таким образом, это решение одного приложения на переднем плане имеет последствия для жизненного цикла приложения. Большинство пользователей Windows Phone очень используется для быстрого перемещения между 3 — й сторона он приложений и самой операционной системы за счет использования кнопок Пуск / Назад, плитки и т.д. Тосты Каждое приложение Windows Phone проходит через несколько этапов, каждый из разоблачающих событий, как мы цикл через время приложения на переднем плане. Подробности следуют.

Заявление Состояния

По сути, каждое приложение Windows Phone проходит через следующие состояния:

  • Запуск — это приоритетное время для любого приложения. ОС и пользователь уделяют приложению все внимание, чтобы выполнять то, что оно делает.
  • Бездействующий — в тот момент, когда приложение выходит на передний план, оно сразу же переходит в стадию бездействия. Это новое усовершенствование в Windows Phone Mango, где все приложение с его историей состояния и backstack сохраняется в памяти без изменений. Целью здесь является удовлетворение максимальной реакции приложений на резюме, которые только что выпали из переднего плана, на случай, если пользователь захочет вернуться в приложение. Это то, что мы называем быстрой коммутацией приложений (FAS) в Windows Phone Mango.

Хочешь попробовать? Нет нужды говорить, что вам нужен Windows Phone под управлением Mango или новейший эмулятор. Переход между несколькими частями операционной системы или через несколько 3 — го приложений сторонних, а затем , удерживая нажатой кнопку Назад .. и вуаля! Вы видите что-то вроде этого, не так ли?

ФАС

То, что вы видите, это обратный стек приложений , занимающий в памяти отпечаток приложений, которые находятся в состоянии бездействия и готовы одним движением вернуться на передний план. Для потребителя это многозадачность. Для вас, как для разработчика, это FAS … очень быстрое возобновление работы приложения без необходимости что-либо делать! Ну, кроме перекомпиляции ваших приложений до Mango с использованием последней версии Windows Phone SDK 7.1 . Это сразу имеет два эффекта — ваше приложение не показывает старое сообщение « Возобновление… », если возвращается на передний план из неактивного состояния, и вы также видите скриншот приложения в backstack приложения. Мы увидим немного кода чуть позже.

  • Tombstoned Теперь, как мы видели, среда выполнения Windows Phone сохраняет бэкстэк неактивных приложений в памяти, которые недавно были на переднем плане. Хотя мгновенное резюме отлично, у любого смартфона ограниченная доступность памяти, и ОС может потребоваться поддержка для выполнения дорогостоящей операции. Угадайте, что будет дальше .. да, приложения падают с backstack в режиме Last-In-First-Out! Другими словами, они захоронены и считаются не потребляющими никаких системных ресурсов после этой точки. Последнее утверждение технически не совсем верно, как мы увидим через минуту.

Итак, как Tombstoning влияет на жизненный цикл приложений? Что ж, если пользователь использовал ваше приложение, а затем перешел на несколько ресурсоемких игр, есть вероятность, что в конечном итоге ваше приложение может быть захоронено, чтобы освободить часть памяти для потребления системой. Однако пользователь может решить пройти обратный стек, чтобы через некоторое время вернуться в ваше приложение. Как должно работать ваше приложение? Можем ли мы вспомнить, что было до того, как пользователь покинул наше приложение?

Ответ — да! Мы можем абсолютно предоставить пользователю опыт, который может показаться таким, как если бы он / она действительно никогда не покидал ваше приложение и начинал с того места, где он остановился. Однако для достижения этого эффекта нам, разработчикам, необходимо разделить некоторую ответственность за управление состоянием нашего приложения на протяжении его жизненного цикла. В следующем коде мы увидим, как мы можем гидрировать наше состояние приложения при возобновлении приложения из Tombstone, чтобы у конечного пользователя создалось впечатление, что приложение фактически никогда не было убито. Правда, даже когда приложение в Tombstoned, некоторые небольшие данные (в виде словаря) о состоянии приложения и обратном стеке страниц в приложении сохраняются в памяти ОС Windows Phone.

  • Завершено — приложение в этом состоянии имеет абсолютно нулевой объем памяти в ОС Windows Phone. Ничто, включая государственные словари, о которых мы поговорим позже, не сохраняется, и любое последующее использование приложения приводит к свежему экземпляру. Приложение достигает этой стадии после того, как оно было захоронено, или пользователь вернулся назад после первой страницы, или произошло необработанное исключение, убивающее приложение.

Страницы и события

Прежде чем перейти к событиям жизненного цикла приложения, давайте сделаем небольшой шаг назад, чтобы убедиться в правильности наших основ. Все приложения Windows Phone Silverlight выполняются как набор страниц XAML, которые загружаются внутри фрейма приложения телефона. Пока приложение находится на переднем плане, пользователь может свободно перемещаться между страницами внутри приложения. Нажатие на кнопку «Назад» на телефоне заставляет пользователя перемещаться по бэкстаку страниц внутри приложения до тех пор, пока не будет нажата первая страница приложения, а затем не будет нажата следующая кнопка «Назад». Нам, как разработчикам, не нужно ничего делать, чтобы это произошло, поскольку ОС Windows Phone об этом позаботится автоматически в прайм-тайм приложения на переднем плане.

Но учтите это … пользователь набирает некоторый контент в элементе управления вводом текста на одной странице XAML, отвлекается и фактически выходит из нашего приложения для выполнения какой-либо операции. Когда он вернется, могут произойти две вещи. Во-первых, приложение могло быть просто бездействующим и возвращалось обратно на передний план, сохраняя то, что печатал пользователь. Ничего не нужно делать вашему приложению, чтобы поддерживать это, кроме проверки API, чтобы убедиться, что ваше приложение было бездействует. Во-вторых, ваше приложение могло быть захоронено, и теперь у вас есть небольшая проблема. Вам необходимо вернуть ваше приложение к жизни и гидрировать состояние страницы XAML в точности так, как это сделал пользователь. Это мы увидим в коде.

Итак, теперь, когда мы говорим о состояниях страницы, самое время поговорить о двух событиях в течение собственной жизни страницы:

  • OnNavigatedTo — это событие возникает, когда страница выводится на передний план для отображения. Это дает нам разработчикам возможность читать строки запросов, переданные на страницу, или соответствующим образом гидрировать элементы управления страницей, если они выходят из Tombstone.
  • OnNavigatedFrom — это событие возникает, когда пользователь покидает текущую страницу, чтобы перейти на другую страницу в приложении или вообще покинуть приложение. В любом случае это служит хорошей контрольной точкой для сохранения любой информации о состоянии для текущей страницы, в случае, если в будущем может потребоваться восстановление. Мы увидим примеры в коде.

Данные и государственные словари

Теперь давайте посмотрим, какие данные мы пытаемся сохранить и увлажнить / обезвожить во время выполнения нашей модели приложения. На мой взгляд, данные приложений на Windows Phone бывают двух типов:

  • Настройки и постоянные данные — этот тип данных является ключевым моментом вашего приложения и должен быть сохранен во всех запусках / экземплярах приложения. Мы не должны тратить время на сохранение таких данных непосредственно в изолированном хранилище, в виде пар «имя / значение» или «файлы / каталоги», или в SQL CE, если данные являются реляционными.
  • Страница и временные данные — это расходные материалы, но они удобны для сохранения данных и в основном обрабатываются в памяти. Этот тип данных может быть любым, начиная от введенных пользователем несохраненных данных на странице или некоторой ViewModel или некоторых временных данных для жизненного цикла приложения, извлекаемых извне. Это данные, с которыми мы должны обращаться осторожно, чтобы у конечных пользователей создалось впечатление, что наше приложение живо и работает в течение всего жизненного цикла. Теперь у нас есть помощь. ОС Windows Phone предоставляет словарь для каждого приложения и каждой страницы приложения, которые можно использовать для хранения сериализуемых данных пары ключ-значение. Этот государственный словарь отключен от PhoneApplicationServiceи должен использоваться только для хранения данных переходного состояния. Вы не должны использовать это свойство для чрезмерного хранения, поскольку существует ограничение в 2 МБ для каждой страницы и 4 МБ для всего приложения; но, как мы увидим в коде, это довольно полезно для поддержания состояния во время модели исполнения нашего приложения. И именно эти словари сохраняются в ОС Windows Phone даже после того, как наше приложение было захоронено. Если приложение повторно активируется из Tombstone, этот словарь состояний будет заполнен данными, которые мы сохранили в нем во время деактивации приложения. Поскольку эти данные присутствуют в памяти, мы можем использовать их для восстановления состояния без ресурсоемких файловых операций.

События приложения

Теперь, когда мы знаем все детали и внутреннюю работу, давайте просто посмотрим, когда мы будем делать то, что происходит в течение жизненного цикла приложения. PhoneApplicationService поднимает 4 основных событий во время исполнения модели приложения , в которое помогает нам управлять государством:

· Запуск — это событие возникает, когда новый экземпляр приложения создается с помощью действий пользователя, таких как запуск из списка приложений, или нажатием на Tile / Toast или другими способами. В этом случае мы начнем с нового листа, а не продолжения предыдущих жизненных циклов приложений.

· Деактивировано — это событие возникает, когда пользователи уходят из нашего приложения или запускается программа выбора. Для нас это лучшая возможность сохранить состояние приложения в словаре состояний для последующего использования при возобновлении работы приложения. В случае выхода из неактивного состояния в этом нет необходимости; но абсолютно необходимо, если возобновление работы с Tombstone и события «Деактивировано» — это единственный шанс сохранить состояние уровня приложения.

· Активировано — это событие вызывается, когда приложение возвращается на передний план из состояния бездействия или захоронения. Угадайте, что … простой флаг API, называемый IsApplicationInstancePreserved от аргументов события, сообщает нам, из какого состояния восстанавливается наше приложение. Если флаг имеет значение true, наше приложение просто неактивно и находится в памяти; поэтому никаких действий не требуется, и FAS запускается. Если флаг равен false, мы возвращаемся из Tombstone и будем использовать словарь состояний для восстановления состояния приложения.

· Закрытие — это событие вызывается, когда пользователь проходит через бэкстек страницы приложения и проходит первую страницу приложения. На этом этапе приложение должно было выйти из переднего плана и завершить себя после возникновения события. Информация о состоянии не сохраняется после завершения, и последующие запуски приложения приведут к новым экземплярам.

Как общая тема, во время любого из указанных выше событий приложения следует избегать ресурсоемких задач, поскольку у нас есть ограниченное время (если быть точным, 10 секунд), чтобы ОС ожидала завершения наших методов. Для лучшего пользовательского опыта некоторые дорогостоящие операции по поддержанию состояния должны выполняться в фоновых потоках во время работы приложения.

Хотите разобраться во всей вышеперечисленной информации? Ну, просто взгляните на общую диаграмму модели выполнения для Windows Phone, лучше всего описанную в статье MSDN:

Модель исполнения

[Предоставлено MSDN]

Демо-приложение

Мы до сих пор общались, не так ли? Давайте посмотрим код! Мы собираемся создать простое приложение для Windows Phone, которое подчеркивает управление состоянием на протяжении всего жизненного цикла приложения. Полное встроенное приложение доступно для скачивания в конце статьи; просто убедитесь, что у вас Windows Phone SDK 7.1, возьмите биты и нажмите F5!

Итак, начнем. Файл -> Новый, и мы принимаем самый основной шаблон по умолчанию для приложения с одной страницей XAML, то есть MainPage.xaml / cs . Мы собираемся открыть страницу XAML и отказаться от некоторых демонстрационных элементов управления ради нас. Конечное состояние выглядит примерно так (нажмите, чтобы увеличить):

Страница XAML

Вот цель … мы собираемся разделить элементы управления для управления состояниями различных типов данных, как описано ниже:

  • Настройка приложения — это пример настройки уровня приложения, которая должна сохраняться во всех экземплярах приложения. Наиболее распространенным примером может быть настройка пользователя для вашего приложения.
  • Данные переходного процесса приложения — этот набор элементов управления будет имитировать данные уровня приложения, которые применимы только для каждого экземпляра приложения, но могут использоваться на нескольких страницах XAML в приложении. Лучшим примером такого рода могут быть данные веб-службы, которые приложение извлекает при каждом запуске заново.
  • Данные о переходных процессах на странице — Этот набор элементов управления обозначает специфичные для страницы данные управления, которые мы, возможно, не хотим потерять, если пользователь выходит из нашего приложения и возвращается к нему через backstack приложения.

Мы собираемся обрабатывать каждый тип данных отдельно. Итак, давайте начнем с App.xaml.cs — места для обработки глобальной логики / данных приложения. Давайте добавим некоторые пользовательские свойства / инициализаторы вместе с небольшим кодом в конструкторе :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
    public partial class App : Application
    {
        public PhoneApplicationFrame RootFrame { get; private set; }

        public bool AppSetting { get; set; }
        public string AppData { get; set; }

        public static new App Current
        {
            get { return Application.Current as App; }
        }

        // Constructor.
        public App()
        {
            // All other initialization code ..
            AppData = string.Empty;

            if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
            {
                AppSetting = (bool)IsolatedStorageSettings.ApplicationSettings["AppSetting"];
            }
        }
    }
}
 

Далее мы добавим некоторый код в обработчики событий жизненного цикла приложения. Обратите внимание на использование флага IsApplicationInstancePreserved, чтобы решить, выйдем ли мы из неактивного / захороненного состояния в обработчике событий Activated приложения . Теперь у меня не было веской причины добавлять какой-либо специальный код в обработчики событий запуска / закрытия приложения ; но вы можете сделать это, если ваше приложение нуждается в таком коде. Кроме того , обратите внимание на использование государственного словаря выходной в PhoneApplicationService :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
    public partial class App : Application
    {
        public PhoneApplicationFrame RootFrame { get; private set; }

        public bool AppSetting { get; set; }
        public string AppData { get; set; }

        public static new App Current
        {
            get { return Application.Current as App; }
        }

        // Constructor.
        public App()
        {
            // All other initialization code ..
            AppData = string.Empty;

            if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
            {
                AppSetting = (bool)IsolatedStorageSettings.ApplicationSettings["AppSetting"];
            }
        }

        private void Application_Launching(object sender, LaunchingEventArgs e)
        {
        }

        private void Application_Activated(object sender, ActivatedEventArgs e)
        {
            if (e.IsApplicationInstancePreserved)
            {
                // Returning from Dormancy.
                // Do nothing.
            }
            else
            {
                // Returning from Tombstone.
                if (PhoneApplicationService.Current.State.ContainsKey("AppData"))
                {
                    AppData = PhoneApplicationService.Current.State["AppData"].ToString();
                }
            }
        }

        private void Application_Deactivated(object sender, DeactivatedEventArgs e)
        {
            PhoneApplicationService.Current.State["AppData"] = AppData;
        }

        private void Application_Closing(object sender, ClosingEventArgs e)
        {
        }

        // Other auto-generated code here ..
    }
}
 

Теперь вернемся к нашему коду MainPage.xaml , давайте добавим обработчики событий для события загрузки страницы и щелчок кнопки, чтобы получить некоторые данные (которые мы полностью фальсифицируем), а также измененное состояние флажка установки приложения. , Обратите внимание, как мы привязываемся к извлеченным данным на уровне приложения, а также как мы немедленно сохраняем настройки приложения / пользователя в изолированном хранилище телефона :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor.
        public MainPage()
        {
            InitializeComponent();
        }

        private void chkAppSetting_Tap(object sender, GestureEventArgs e)
        {
            // Store App Setting changes immediately in Isolated Storage for persistence.
            if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
            {
                IsolatedStorageSettings.ApplicationSettings.Remove("AppSetting");
            }

            if ((bool)this.chkAppSetting.IsChecked)
            {
                IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", true);
            }
            else
            {
                IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", false);
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.txtAppData.Text = "This is some sample data ..";

            // As if we are caching the data.
            App.Current.AppData = this.txtAppData.Text;
        }

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.chkAppSetting.IsChecked = App.Current.AppSetting;
        }
    }
}
 

Теперь давайте добавим все важные события навигации по страницам. Вот где мы можем контролировать, как мы гидратируем / обезвоживаем переходные данные, когда страница XAML выходит на передний план и выходит из нее:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor.
        public MainPage()
        {
            InitializeComponent();
        }

        private void chkAppSetting_Tap(object sender, GestureEventArgs e)
        {
            // Store App Setting changes immediately in Isolated Storage for persistence.
            if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
            {
                IsolatedStorageSettings.ApplicationSettings.Remove("AppSetting");
            }

            if ((bool)this.chkAppSetting.IsChecked)
            {
                IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", true);
            }
            else
            {
                IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", false);
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Fake data fetch..
            this.txtAppData.Text = "This is some sample data ..";

            // As if we are caching the data.
            App.Current.AppData = this.txtAppData.Text;
        }

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            // Load user setting from Isolated Storage.
            this.chkAppSetting.IsChecked = App.Current.AppSetting;
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            // Load in App Data, if available.
            if (App.Current.AppData != null && App.Current.AppData != string.Empty)
            {
                this.txtAppData.Text = App.Current.AppData;
            }

            // Load any Page State, if available.
            if (PhoneApplicationService.Current.State.ContainsKey("PageData"))
            {
                this.txtPageData.Text = PhoneApplicationService.Current.State["PageData"].ToString();
            }
            if (PhoneApplicationService.Current.State.ContainsKey("PageSetting"))
            {
                this.chkPageSetting.IsChecked = (bool)PhoneApplicationService.Current.State["PageSetting"];
            }
        }

        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);

            // Clear past state, if any.
            if (PhoneApplicationService.Current.State.ContainsKey("PageData"))
            {
                PhoneApplicationService.Current.State.Remove("PageData");
            }

            if (PhoneApplicationService.Current.State.ContainsKey("PageSetting"))
            {
                PhoneApplicationService.Current.State.Remove("PageSetting");
            }

            // Save off Page state.
            PhoneApplicationService.Current.State["PageData"] = this.txtPageData.Text;
            PhoneApplicationService.Current.State["PageSetting"] = this.chkPageSetting.IsChecked;
        }
    }
}

Это оно! Теперь мы добавили достаточно кода, чтобы различные типы данных / состояний управлялись на протяжении жизненного цикла приложения, каждый из которых по-своему подходил. Иди вперед и дать ему вращение. Запустите приложение, внесите изменения в настройки, перейдите куда-нибудь еще и вернитесь или выйдите и запустите свежий экземпляр приложения. Данные и информация о состоянии должны быть сохранены и стерты, как и ожидалось. Типичный прогон с включенными настройками выглядит примерно так:

Приложение и состояние страницы

Теперь вы можете заметить одну вещь как на эмуляторе, так и в случае его развертывания на реальном телефоне. Когда мы перебираем приложение, выходим из него и затем возвращаемся к нему во время выполнения Windows Phone Mango, трудно предсказать, когда наше приложение будет фактически захоронено. В большинстве случаев, если приложение отключено, оно просто находится в состоянии покоя и возвращается к жизни. Итак, хотя у нас есть код для настройки параметров при возвращении из Tombstone, нет определенного способа проверить это.

Не беспокойся! Существует простая настройка отладки, которая принудительно захоронит ваше приложение в момент его деактивации. Таким образом, даже если приложение может оставаться неактивным, этот параметр, показанный ниже, заставляет его быть захороненным. Это, очевидно, помогает нашему тестированию.

Установка надгробной плиты

Резюме

Добавление точек останова к различным обработчикам событий и наблюдение за их срабатыванием в течение жизненного цикла приложения — отличный способ понять модель выполнения. И так, чего же ты ждешь? Идите вперед и в полной мере используйте функцию быстрого переключения приложений и корректно увлажняйте / обезвоживайте состояние своего приложения / страницы на протяжении всего жизненного цикла приложения.

Результат — Счастливые пользователи, и в конечном итоге счастливее вас! Прощайте!

Чтобы загрузить полный проект Windows Phone, который включает в себя все концепции, описанные в этой статье, нажмите кнопку «Загрузить код» ниже.

скачать

Завтра Крис Кениг расскажет о другом замечательном инструменте, доступном нам в Visual Studio 2010, инструменте анализа производительности Silverlight. Увидимся позже!

toolsbutton

Источник: http://www.jeffblankenburg.com/2011/11/23/31-days-of-mango-day-23-execution-model/