Read this article in your language IT | EN | DE | ES
В Silverlight, по-умолчанию, весь пользовательский код выполняется в потоке интерфейса. Тем самым при длительных операциях мы может заблокировать не только Silverlight приложение, но и весь браузер. Что является не дружественным поведенем нашего приложения по отношеню к пользователям. Для решения этой проблемы можно запускать паралельные потоки или использовать пул потоков. И уже непосредственно в паралельных потоках запускать длительные по времени операции. При этом в интерфейсе можно отобразить индикатор работы процесса(например BusyIndicator).
Для эмуляции длительной операции создадим новое Silverlight приложение и в коде добавим следующее
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.Threading;
namespace Multithreading.Dispatcher
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
for (int z = 0; z < 1000000; z++)
{
for (int i = 0; i < 1000000; i++)
{
int j = i;
}
}
}
}
}
При запуске такого приложения мы получем заблокированное окно браузера:
Чтобы решить эту проблему изменим наше тестовое приложение так, чтобы длительная операция выполнялась в другом потоке:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
{
for (int z = 0; z < 100000; z++)
{
for (int i = 0; i < 10000; i++)
{
int j = i;
}
}
}), null);
busyIndicator.IsBusy = true;
}
Результат работы наших изменений смотрите ниже:
Но здесь мы получаем проблему синхронизации данных между потоком интерфейса и "рабочим" потоком. Например в нашем примере нам необходимо спрятать BusyIndicator по окончанию длительной операции. Для решения этой задачи инфраструктура Silverlight предоставляет несколько способов:
Использование Dispatcher
Dispatcher предоставляет возможность выполнения кода в потоке интерфейса из неинтерфейсного потока. Для нашего примера нужно изменить код на следующий:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
{
for (int z = 0; z < 100000; z++)
{
for (int i = 0; i < 10000; i++)
{
int j = i;
}
}
Dispatcher.BeginInvoke(new Action(() =>
{
busyIndicator.IsBusy = false;
}));
}), null);
busyIndicator.IsBusy = true;
}
Через метод BeginInvoke() мы указываем какой код необходимо выполнить в потоке интерфейса.
Использование SynchronizationContext
SynchronizationContext обеспечивает базовую функциональность для синхронизации контекста в различных моделях синхронизации. Данный подход является более мощным, чем использование Dispatcher. Например он позволяет выполнять код синхронно или асинхронно в потоке интерфейса по отношению к текущему потоку. При использовании контекста синхронизации его необходимо передать в "рабочий" поток. Пример:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
{
for (int z = 0; z < 100000; z++)
{
for (int i = 0; i < 10000; i++)
{
int j = i;
}
}
((SynchronizationContext)obj).Post(new SendOrPostCallback((parameter) =>
{
busyIndicator.IsBusy = false;
}), null);
}), SynchronizationContext.Current);
busyIndicator.IsBusy = true;
}
Исходный код: