Как сохранить асинхронный параллельный программный код управляемым (например, в C ++) - PullRequest
4 голосов
/ 25 июня 2009

В настоящее время я работаю над серверным приложением, которое должно управлять устройствами сбора данных по сети. Из-за этого нам нужно много параллельного программирования. Со временем я узнал, что существует три подхода к взаимодействию между объектами обработки (потоками / процессами / приложениями). К сожалению, все три подхода имеют свои недостатки .

A) Вы можете сделать синхронный запрос (синхронный вызов функции). В этом случае вызывающая сторона ожидает, пока функция не будет обработана и ответ не будет получен. Например:

const bool convertedSuccessfully = Sync_ConvertMovie(params);

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

B) Вы можете сделать асинхронный запрос и дождаться обратного вызова . Код клиента может продолжаться с любыми необходимыми действиями.

Async_ConvertMovie(params, TheFunctionToCallWhenTheResponseArrives);

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

void TheFunctionToCallWhenTheResponseArrives()
{
    //Difficulty 1: how to get to the dialog instance?
    //Difficulty 2: how to guarantee in a thread-safe manner that
    //              the dialog instance is still valid?
}

Само по себе это не такая большая проблема. Однако, когда вы хотите сделать более одного из таких звонков, и все они зависят от ответа предыдущего, в моем опыте это становится неуправляемо сложным .

C) Последний вариант, который я вижу, - сделать асинхронный запрос и продолжать опрашивать до получения ответа. В промежутках между проверками «есть ответ, пришедший еще» вы можете сделать что-то полезное. Это лучшее из известных мне решений для решения случая, когда необходимо выполнить последовательность асинхронных вызовов функций. Это потому, что у него есть большое преимущество в том, что у вас все еще есть весь контекст вызывающей стороны, когда приходит ответ. Кроме того, логическая последовательность вызовов остается достаточно ясной. Например:

const CallHandle c1 = Sync_ConvertMovie(sourceFile, destFile);
while(!c1.ResponseHasArrived())
{
    //... do something in the meanwhile
}
if (!c1.IsSuccessful())
    return;

const CallHandle c2 = Sync_CopyFile(destFile, otherLocation);
while(!c1.ResponseHasArrived())
{
    //... do something in the meanwhile
}
if (c1.IsSuccessful())
    //show a success dialog

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

Примечание: приведен пример псевдокода, подобного C ++. Тем не менее, я думаю, что этот вопрос в равной степени относится к C # и Java, и, возможно, ко многим другим языкам.

Ответы [ 3 ]

3 голосов
/ 25 июня 2009

Вы могли бы рассмотреть явный "цикл обработки событий" или "цикл обработки сообщений", не слишком отличающийся от классических подходов, таких как цикл select для асинхронных сетевых задач или цикл обработки сообщений для оконной системы. События, которые приходят , могут отправляться в обратный вызов, когда это уместно, например, в вашем примере B, но они также могут в некоторых случаях отслеживаться по-разному, например, вызывать транзакции в конечном автомате. FSM - это прекрасный способ управлять сложностью взаимодействия по протоколу, который, в конце концов, требует много шагов!

Один из подходов к систематизации этих соображений начинается с схемы проектирования Reactor .

Работа Шмидта ACE является хорошей отправной точкой для решения этих проблем, если вы пришли из C ++; Twisted также весьма полезен, на фоне Python; и я уверен, что аналогичные рамки и наборы технических документов существуют, как вы говорите, для «многих других языков» (URL-адрес Википедии, который я дал, указывает на реализации Reactor для других языков, кроме ACE и Twisted).

2 голосов
/ 25 июня 2009

Я склонен идти с B, но вместо того, чтобы звонить вперед и назад, я бы сделал всю обработку, включая последующие действия в отдельном потоке. Тем временем основной поток может обновлять графический интерфейс и либо активно ожидать завершения потока (то есть показать диалоговое окно с индикатором выполнения), либо просто позволить ему выполнить свою работу в фоновом режиме и получить уведомление, когда это будет сделано. Пока никаких проблем со сложностью, так как вся обработка на самом деле является синхронной с точки зрения потока обработки. С точки зрения графического интерфейса, он асинхронный.

Кроме того, в .NET нет проблем переключиться на поток GUI. Класс BackgroundWorker и ThreadPool также упрощают эту задачу (я использовал ThreadPool, если я правильно помню). В Qt, например, остаться с C ++ тоже довольно просто.

Я использовал этот подход в нашем последнем крупном приложении и очень доволен им.

1 голос
/ 25 июня 2009

Как сказал Алекс, посмотрите на Proactor и Reactor, как описано Дугом Шмидтом в разделе «Образцы архитектуры программного обеспечения».

В ACE есть их конкретные реализации для разных платформ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...