Создание бизнес-приложений с помощью Silverlight - Синхронизированные вызовы служб
ОГЛАВЛЕНИЕ
Синхронизированные вызовы служб
Вследствие асинхронной природы сетевой среды Silverlight практически невозможно осуществить асинхронный вызов в потоке пользовательского интерфейса и ждать его выполнения с намерением изменить состояние приложения на основе результатов вызова. На рис. 4 _notifyCallPopup_OnAccept должен получить сведения о заказе, преобразовать выходное сообщение в объект клиента и сохранить его в глобальной переменной безопасным с точки зрения потока способом. При выполнении этой задачи можно поддаться искушению и написать код обработчика, как показано ниже.
CallServiceClient client = new CallServiceClient();
client.GetOrderDetailsAsync(orderNumber);
this._orderDetailDownloadHandle.WaitOne();
//do something with the results
Но этот код заблокирует приложение, как только оно дойдет до оператора this._orderDetailDownloadHandle.WaitOne(). Это обусловлено тем, что оператор WaitOne() блокирует для потока пользовательского интерфейса возможность получения любых сообщений, направленных из других потоков. Вместо этого можно предусмотреть, чтобы рабочий поток выполнял вызов службы, ждал завершения вызова, и чтобы завершение последующей обработки результатов работы службы в целом происходило в рабочем потоке. Эта методика показана на рис. 5. Для предотвращения непреднамеренного использования блокирующих вызовов в потоке пользовательского интерфейса я заключил ManualResetEvent в пользовательский SLManualResetEvent и делаю проверку для потока пользовательского интерфейса, когда осуществляется вызов WaitOne().
Рис. 5 Получение сведений о заказе
void _notifyCallPopup_OnAccept(object sender, EventArgs e)
{
...
ThreadPool.QueueUserWorkItem(ExecuteGetOrderDetails,
ClientGlobals.callInfo.OrderNumber);
}
private SLManualResetEvent _ orderDetailDownloadHandle = new
SLManualResetEvent();
private void ExecuteGetOrderDetails(object state)
{
CallServiceClient client = new CallServiceClient();
string orderNumber = state as string;
client.GetOrderDetailsCompleted += new
EventHandler<GetOrderDetailsCompletedEventArgs>
(GetOrderDetailsCompletedCallback);
client.GetOrderDetailsAsync(orderNumber);
this._orderDetailDownloadHandle.WaitOne();
//translate entity and save it to global variable
ClientEntityTranslator oito = SvcOrderToClientOrder.entityTranslator;
ClientEntities.Order currentOrder =
oito.ToClientEntity(ClientGlobals.serviceOutputOrder)
as ClientEntities.Order;
Interlocked.Exchange<ClientEntities.Order>(ref ClientGlobals.
currentOrder, currentOrder);
}
void GetOrderDetailsCompletedCallback(object sender,
GetOrderDetailsCompletedEventArgs e)
{
Interlocked.Exchange<OrderInfo>(ref ClientGlobals.serviceOutputOrder,
e.Result);
this._orderDetailDownloadHandle.Set();
}
Поскольку SLManualResetEvent является классом общего назначения, вы не зависите от метода Dispatcher.CheckAccess() конкретного элемента управления. ApplicationHelper.IsUiThread() может проверить Application.RootVisual.Dispatcher.CheckAccess(); однако, доступ к этому методу запускает исключение недопустимого межпотокового доступа. Поэтому единственным надежным способом проверки этого в рабочем потоке, когда нет никакого доступа к экземпляру UIElement, является использование метода Deployment.Current.Dispatcher.CheckAccess(), как показано ниже.
public static bool IsUiThread()
{
if (Deployment.Current.Dispatcher.CheckAccess())
return true;
else
return false;
}
Для фонового выполнения задач вместо использования метода ThreadPool.QueueUserWorkItem можно использовать BackGroundWorker, который также использует ThreadPool, но позволяет устанавливать связь с обработчиками, которые могут выполняться в потоке пользовательского интерфейса. Данный шаблон позволяет выполнять несколько вызовов служб параллельно и ждать завершения всех вызовов, используя метод SLManualResetEvent.WaitOne(), прежде чем результаты будут объединены для последующей обработки.