Как сериализовать доступ к асинхронному ресурсу? - PullRequest
1 голос
/ 03 февраля 2011

Может кто-нибудь указать мне на хорошее рабочее решение следующей проблемы?

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

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

Мне нужно разрешить обрабатывать только один запрос за один раз, то есть он должен быть сериализован, иначе это плохослучается с TCP-связями.

Попытки решения до сих пор

Изначально я пытался использовать lock () со статическим объектом для защиты каждого метода 'command',следующим образом:

lock (_cmdLock)
{
SetPosition(position);
}

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

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

   private static AutoResetEvent _cmdProcessorReadyEvent = new AutoResetEvent(false);
   private static AutoResetEvent _resultAvailableEvent = new AutoResetEvent(false);
   private static AutoResetEvent _sendCommandEvent = new AutoResetEvent(false);

   // This method is called to send each command and can run on different threads
   private bool SendCommand(Command cmd)
    {
       // Wait for processor thread to become ready for next cmd
       if (_cmdProcessorReadyEvent.WaitOne(_timeoutSec  + 500))
            {
            lock (_sendCmdLock)
                 {
                 _currentCommand = cmd;
                 }

            // Tell the processor thread that there is a command present
            _sendCommandEvent.Set();

            // Wait for a result from the processor thread
            if (!_resultAvailableEvent.WaitOne(_timeoutSec  + 500))
                _lastCommandResult.Timeout = true;

        }
        return _lastCommandResult.Success;
    }

 // This method runs in a background thread while the app is running
 private void ProcessCommand()
    {
        try
        {
            do
            {
                // Indicate that we are ready to process another commnad
                _cmdProcessorReadyEvent.Set();

                _sendCommandEvent.WaitOne();
                lock (_sendCmdLock)
                {
                    _lastCommandResult = new BaseResponse(false, false, "No Command");
                    RunCOMCommand(_currentCommand);
                }

                _resultAvailableEvent.Set();

            } while (_processCommands);
        }
        catch (Exception ex)
        {
            _lastCommandResult.Success = false;
            _lastCommandResult.Timeout = false;
            _lastCommandResult.LastError = ex.Message;
        }

    }

Я не пытался реализовать очередьЗапросы команд как вызывающий код ожидают, что все будет синхронно - т.е. предыдущая команда должна быть выполнена до того, как я отправил следующую.

Дополнительный фон

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

Я на самом деле использую устаревшую DLL-библиотеку VB6 для связи с лазером, поскольку в ней есть весь код для форматирования команд и обработки ответов.Эта библиотека VB6 использует элемент управления WinSock для связи.

1 Ответ

4 голосов
/ 03 февраля 2011

Я не уверен, почему решение для очередей не будет работать.

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

Я думаю, что это более четкое разделение проблем между модулями, чем реализация блокировки при отправке запросов и т. Д. Ваш запросчик в значительной степени не замечает ограничений сериализации, а сторонний интерфейсный модуль может следить за сериализацией, управлять таймаутами идругие ошибки и т. д.

Редактировать: В мире Java у нас есть BlockingQueues, которые синхронизируются для потребителей / издателей и делают подобные вещи довольно простыми.Я не уверен, есть ли у вас то же самое в мире C #.Быстрый поиск подсказывает, что нет, но есть исходный код, плавающий вокруг для такого рода вещей (если кто-то в мире C # может пролить некоторый свет, который был бы оценен)

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