等待TIdHttpServer的OnCommandGet事件处理程序内的线程执行

时间:2017-01-09 22:21:24

标签: multithreading delphi async-await idhttp

我的Delphi Berlin应用程序使用TIdHttpServer通过HTTP GET从客户端获取一些数据,处理并发回。

所有逻辑都在单个事件处理程序中执行:OnCommandGet。标识符在QueryString中接收,然后数据将被转换并返回到同一OnCommandGet事件处理程序内的客户端。

数据转换在一个单独的线程中实现,该线程使用PostMessage通知主线程工作线程完成执行并且数据已准备好发送回客户端。

数据以AResponseInfo.ContentText属性发送。

我的问题是:

  

如何使OnCommandGet处理程序等待直到工作线程   做它的工作,并将指针发送到转换后的数据,所以我可以得到   价值并将其重新计入AResponseInfo.ContentText


更新 这是我想要执行的伪代码:

type
  TMyResponsesArray = array[0..5] of TMyObjectAttributes;
  PMyResponsesArray = ^TMyResponsesArray;

{There will be 6 tasks run in parallel. Tasks' responses
will be stored in the below declared Responses array.}

var
  Responses: TMyResponsesArray;

{Below is a Server handler, which takes the input parameter and calls
a proc which runs 6 threads in parallel. The result of each thread is
stored as an ordered array value. Only when the array is completely
populated, ServerCommandGet may send the response!}

procedure TMainForm.ServerCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  ObjectId: string;
begin
  ObjectId := ARequestInfo.Params.Values['oid'];
  RunTasksInParallel(ObjectId);
end;

{Below is a procedure invoked by ServerCommandGet. It runs 6 tasks in
parallel. Each of the thread instantiates an object, sets its basic
parameter and fires the method. Each task runs queued. When each thread
completes the job, it sends a WM to the main thread (via ParentHandler
which must accept and process the response.}

procedure TMainForm.RunTasksInParallel(const ObjectId: string);
const
  c: array[0..5] of Byte = (0, 1, 2, 3, 4, 5);
var
  ParentHandle: HWND;
begin

  {running 6 tasks in parallel}
  TTask.Run(
    procedure
    begin
      TParallel.For(Low(c), High(c),
        procedure(index: Integer)
        var
          MyObj: TMyObject;
          i: Byte;
        begin
          i := c[index];
          MyObj := TMyObject.Create;

          try
            MyObj.SetMyParameter := Random(10);
            Responses[i] := MyObj.CallMyMethd(ObjectId);

            TThread.Queue(nil,
              procedure
              begin
                SendMessage(ParentHandle,
                  UM_DATAPACKET, i, Integer(@Responses));
              end);

          finally
            MyObj.Free;
          end;

        end);
    end);
end;

{Now the WM handler. It decreases internal task counter and when
TaskCounter = 0, it means that all tasks finished execution and the
Responses array is fully populated. Then we somehow need to pass the
Response array to the ServerCommandGet and send it back to client...}

procedure TMainForm.OnDataPacket(var Msg: TMessage);
begin
  i := Msg.WParam;
  Responses := PMyResponsesArray(Msg.LParam)^;

  {Skipped for for brevity:
  When ALL tasks have finished execution, the Responses array is FULL.
  Then all array values are wrapped into XML and sent back to initial
  invoker ** ServerCommandGet ** which must send XML to client.}
end;

1 个答案:

答案 0 :(得分:1)

您使用全局Responses数组并不安全,除非您将TIdHTTPServer限制为一次只允许一个连接的客户端。否则,您可能会有多个客户端同时发送请求并覆盖数组中的彼此值。 ServerCommandGet()的每次调用都应使用本地数组。

TIdHTTPServer不适用于您尝试执行的异步处理类型。 ServerCommandGet()必须阻止,因为TIdHTTPServerOnCommandGet处理程序退出时向客户端发送响应,除非处理程序首先发送响应,而您没有执行此操作。所以,关于你的任务线程管理,我建议:

  1. 摆脱TTask.Run()RunTasksInParallel()直接致电TParallel.For()

  2. 或至少在调用TTask.Wait()的{​​{1}}对象上调用TTask

  3. 无论哪种方式都会使TParallel.For()阻塞(从而使RunTasksInParallel()阻止)直到所有任务都完成。然后,您可以在ServerCommandGet()退出时立即将响应发送给客户端。您无需等待任务将RunTasksInParallel()发布到主线程并往返回UM_DATAPACKET。如果您将TIdHTTPServer用于其他事情,那很好,但我不建议您将其用于HTTP处理。

    尝试更像这样的东西:

    UM_DATAPACKET

    我也不建议在主线程中进行数据库更新。如果您无法直接在const MaxResponses = 6; type TMyResponsesArray = array[0..MaxResponses-1] of TMyObjectAttributes; {$POINTERMATH ON} PMyResponsesArray = ^TMyResponsesArray; {There will be 6 tasks run in parallel. Tasks' responses will be stored in the below declared Responses array.} procedure TMainForm.ServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var ObjectId: string; Responses: TMyResponsesArray; begin ObjectId := ARequestInfo.Params.Values['oid']; RunTasksInParallel(ObjectId, @Responses); {ALL tasks have finished execution, the Responses array is FULL. Wrap all array values into XML and send it back to the client.} end; {Below is a procedure invoked by ServerCommandGet. It runs 6 tasks in parallel. Each of the thread instantiates an object, sets its basic parameter and fires the method.} procedure TMainForm.RunTasksInParallel(const ObjectId: string; Responses: PMyResponsesArray); begin {running 6 tasks in parallel} TParallel.For(0, MaxResponses-1, procedure(index: Integer) var MyObj: TMyObject; begin MyObj := TMyObject.Create; try MyObj.SetMyParameter := Random(10); Responses[index] := MyObj.CallMyMethd(ObjectId); finally MyObj.Free; end; end ); end; 中更新数据库,或者直接在单个任务线程中更新数据库,那么我建议您根据需要使用专门用于数据库更新的单独线程。尽可能远离主线程。