Delphi - Асинхронные вызовы метода Datasnap - PullRequest
0 голосов
/ 08 января 2019

Я пишу приложение Datasnap с TCP-соединением между клиентом и сервером, с сервером, подключенным к SQL-серверу.

Сервер имеет модуль данных DM1 со всеми запросами к набору данных и соединением SQL. DM1 также имеет компоненты REST-запрос / клиент / ответ.

DM1 имеет открытую функцию PostDataAsync с идентификатором param: для генерации json из набора данных, а затем HTTP отправляет его в службу RESTFul. Возвращает количество записей, которые не удалось опубликовать в аргументе обратного вызова.

DSServer этого DM1 - «Invocation».

Тип сервера вызова должен обеспечивать, чтобы каждый вызов метода сервера имел свое собственное соединение с БД, набор данных, компоненты Rest, которые не будут иметь множественные вызовы, мешающие данным друг друга (если добавлена ​​параллельная многопоточность).

procedure TServerMethods1.postCustOrderHistAsync(CustomerID: String; callback: TDBXcallback);
var
  jsonObject: TJSONObject;
  CallbackValue: TJsonValue;
  errors: Integer;
begin
  errors := postCustOrderHist(CustomerID); //takes time to post, returns num of failed records
  jsonObject := TJSONObject.create;
  jsonObject.AddPair(tjsonpair.create('errors', errors.ToString));
  CallbackValue := callback.Execute(jsonObject);
end;

Клиент имеет кнопку, которая вызывает метод сервера PostDataAsync с ID param, а также функцию обратного вызова «ShowNotification» (которая использует центр уведомлений Windows для отображения статуса уведомления Post).

На данный момент приложение работает следующим образом: клиент вызывает функцию сервера синхронно, это означает, что основной поток ожидает, пока функция сервера завершит HTTP-публикацию, а затем запускает уведомление об обратном вызове; клиент тем временем висит.

TDSCallbackWithMethod = class(TDBXCallback)
private
  FCallbackMethod: TDSCallbackMethod;
public
  constructor Create(ACallbackMethod: TDSCallbackMethod);
  function Execute(const Args: TJSONValue): TJSONValue; override; //executes FCallbackMethod
end;

procedure TMainForm.BtnPostOrderHistoryClick(Sender: TObject);
var
  callback: TDBXCallback;
  ServerMethods1Client: TServerMethods1Client;
begin
  //Define Callback to show notification
  callback := TDSCallbackWithMethod.Create(
    function(const Args: TJSONValue): TJSONValue
    var
      errors: integer;
    begin
      errors := Args.GetValue<integer>('errors');

      if errors = 0 then
        showNotification(StrSentSuccessfully)
      else
        showNotification(StrSendingFailed + '(' + errors.ToString + ' not sent)');

      result := TJsonTrue.Create;
    end);

  //Call Server Method
  ServerMethods1Client := TServerMethods1Client.Create(DMServerConnection.SQLConnection1.DBXConnection);
  try
    ServerMethods1Client.postCustOrderHistAsync(EditCustomerId.Text, callback)
  finally
    ServerMethods1Client.Free;
  end;
end;

Каким должен быть проект, чтобы асинхронно вызывать методы сервера и позволить серверу выполнить обратный вызов по завершении? Функция Post должна вызываться несколько раз одним и тем же пользователем или несколькими пользователями одновременно. Должен ли поток быть на стороне сервера или на стороне клиента? Если кто-то может помочь с этим, я могу отправить демонстрационную версию приложения, используя базу данных Northwind.

Примечание: я попытался запустить вызов клиентской функции в TTask, он работает, когда пользователь запускает функцию по одному за раз. Но когда серверный метод запускается одновременно несколько раз, я получаю «DBXError… Ошибка чтения… обратный вызов, ожидающий X получил Y». Кажется, что пока клиент ожидает формат ответного обратного вызова из первого запроса, он путается с другими пакетами протокола tcp, инициированными из второго запроса. Я попытался запустить ttask на стороне сервера, я получил исключение "TOLEDBCommand.Destroy - интерфейсы не выпущены"

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Для упрощения вызова метода на стороне сервера я удалил Callback из клиента и просто создал параллельные потоки, которые ожидают ответа от сервера. Я все еще получаю ту же ошибку «DBXError… Ошибка чтения… обратный вызов, ожидающий, что X получил Y». И вот тогда я понял, что ошибка не в проблеме обратного вызова, а в помехах между потоками. Оказалось, что когда я создавал прокси-методы клиента, все потоки использовали один и тот же экземпляр DBXConnection. Это приведет к потере SQLconnection между различными вызовами / ответами сервера и получит ошибку разбора. Я сделал функцию «getNewSqlConnection», которая будет копировать все настройки TSQLConnection в новый экземпляр.
Теперь метод вызова клиента выглядит так:

procedure TMainForm.BtnPostOrderHistoryClick(Sender: TObject);
begin
  ttask.Run(
    procedure
    var
      ServerMethods1Client: TServerMethods1Client;
      SqlConnectionLocal: TSqlConnection;
      errors: Integer;
    begin
      // Call Server Method
      SqlConnectionLocal := DMServerConnection.getNewSqlConnection(Self);
      ServerMethods1Client := TServerMethods1Client.Create(SqlConnectionLocal.DBXConnection);
      try
        errors := ServerMethods1Client.postCustOrderHist(EditCustomerId.Text);
        if errors = 0 then
          TThread.Synchronize(nil,
            Procedure
            begin
              showNotification(StrSentSuccessfully)
            end)
        else
          TThread.Synchronize(nil,
            Procedure
            begin
              showNotification(StrSendingFailed + '(' + errors.ToString + ' not sent)')
            end);
      finally
        ServerMethods1Client.Free;
        SqlConnectionLocal.Free;
      end;
    end);
end;
0 голосов
/ 08 января 2019

Проверьте в этом примере, он проходит через шаги для создания обратного вызова. По сути, вам нужен TDSClientCallbackChannelManager (компонент) и его функция RegisterCallback, чтобы сообщить клиенту datasnap, какой метод (объекта, унаследованного от TDBXCallback) вызывать на стороне клиента при вызове обратного вызова с сервера. Вам нужно будет передать идентификаторы сеанса клиентов на сервер, чтобы он мог вызвать правильного клиента с помощью NotifyCallBack. Затем из этого метода обратного вызова вы можете сделать то, что вам нужно, в TThread.Queue для безопасности. Возможно, вам потребуется создать какой-то уникальный идентификатор (или, возможно, ваш CustomerID будет работать) в JSON, который возвращает сервер, чтобы ваш клиент знал, какой вызов является результатом какого.

...