2017-09-05 13 views
6

Tôi tương đối mới đối với các công cụ RestRequest. Tôi hy vọng ai đó đã giải quyết vấn đề chờ cuộc gọi async kết thúc ...Delphi Threading Với TRestRequest

Tôi có một chương trình tạo ra hàng trăm cuộc gọi api khác nhau, cuộc gọi sau đó xử lý kết quả và chuyển kết quả trở lại quy trình được gọi là sự thực hiện của api, sau đó có thể tiếp tục ...

Chỉ có vấn đề như khi thực hiện các cuộc gọi như vậy mà không cần luồng, là phần mềm bị treo cho đến khi cuộc gọi kết thúc ... Để thử và khắc phục điều này, tôi đã thay đổi RESTRequest.Execute; thành RESTRequest.ExecuteAsync();, nhưng bây giờ vấn đề của tôi là mã của tôi tiếp tục mà không cần chờ phản hồi của yêu cầu.

một lần nữa để cố gắng và bỏ qua vấn đề này, tôi đã cố gắng một số giải pháp, thậm chí api.RESTRequest.ExecuteAsync().WaitFor; (mà ném một lỗi thread error: the handler is invalid (6))

Có cách nào tại tất cả những gì tôi có thể thay đổi các chức năng dưới đây để chạy như một sợi riêng biệt (chỉ phần thực thi là quan trọng để chạy trong chuỗi) ... Về cơ bản tôi chỉ muốn hiển thị một biểu tượng tải động mỗi khi hàm được gọi, và cho phần còn lại của mã của tôi để wat cho đến khi hàm này hoàn thành ...

Tôi hy vọng có một giải pháp đơn giản hơn để bắt đầu sử dụng đầy đủ trên đa luồng ..

function run_api_command():boolean; 
begin 

    result := false; 
    RESTResponse.Content.Empty; 
    RESTAdapter.Dataset:= ds_action; 
    RESTAdapter.RootElement:= ''; 

    try 
    RESTRequest.ExecuteAsync; 

    if(ds_action.Active = false) then ds_action.Active:=true; 
    if(ds_action.FieldByName('result').AsString<>'Success') then 
     begin 
     showmessage(ds_action.FieldByName('result').AsString); 
     end 
    else 
     begin 
     RESTAdapter.RootElement:= 'data'; 
     result := true; 
     end; 
    except 
    on E: Exception do 
     begin 
     if(e.Message = 'REST request failed: Error sending data: (12007) The server name or address could not be resolved') then 
      begin 
      if(messagedlg('Could not connect to server. Would you like to retry?', mterror,[mbYes,mbNo],0)=mrYes) then 
       begin 
       result := run_api_command(); 
       end; 
      end 
     else 
      begin 
      showmessage(RESTResponse.Content); 
      end; 
     end; 
    end; 
end; 

Trả lời

7

ExecuteAsync() chạy trong một sợi công nhân (đối tượng TRESTExecutionThread rằng nó sẽ trả về). Tuy nhiên, ExecuteAsync() có thông số AFreeThread mặc định là True. Khi documentation nêu rõ:

Khi tham số AFreeThread được đặt thành False, phương pháp này trả về một reference quan đến chủ đề thực thi này.

Lưu ý: Nếu tham số AFreeThread được đặt thành True, phương thức trả về tham chiếu không hợp lệ.

Vì vậy, gọi số WaitFor() trên con trỏ đối tượng được trả về sẽ bị lỗi theo mặc định.

Ngay cả khi con trỏ đối tượng được trả lại hợp lệ khi AFreeThread=True, gọi số WaitFor() vẫn sẽ bị lỗi. Khi một đối tượng của đối tượng là FreeOnTerminate được đặt thành True, đối tượng giải phóng xử lý API cơ bản của nó khi chuỗi kết thúc, gây ra lỗi WaitFor() không thành công với lỗi "xử lý không hợp lệ". TThread.WaitFor() có lỗi logic không xử lý trường hợp khi TThread.FreeOnTerminate=True.

(Lỗi đó được giới thiệu ngược lại trong Delphi 6, khi TThread được viết lại để hỗ trợ Kylix và nó không bao giờ được sửa chữa trong các phiên bản sau.)

Nếu bạn muốn người gọi để chờ một phản hồi từ máy chủ REST, một trong hai:

  • sử dụng Execute() thay vì ExecuteAsync(), hoặc ít nhất là thiết lập AFreeThread=False vì vậy bạn có thể gọi WaitFor() (hoặc tương đương, như MsgWaitForMultipleObjects()) trên đối tượng thread, và sau đó đối phó với những hậu quả của việc thực hiện một chờ đợi trong thread UI chính:

  • sử dụng Execute(), nhưng di chuyển toàn bộ logic của bạn để sợi nhân của riêng bạn, và có nó đồng bộ với giao diện người dùng chính chuỗi dưới dạng cần thiết.

Giải pháp tốt hơn chỉ đơn giản là không chờ chút nào, có nghĩa là thiết kế lại luồng mã của bạn. Tiếp tục sử dụng ExecuteAsync(), nhưng chuyển cho nó một cuộc gọi lại hoàn thành sẽ được gọi khi yêu cầu hoàn tất và cho phép cuộc gọi lại đó bước tiếp theo trong mã của bạn. Đừng chủ động chờ ExecuteAsync để hoàn tất, hãy để nó thông báo cho bạn.

procedure run_api_command(); 
begin 
    ... 
    RESTRequest.ExecuteAsync(
    procedure 
    begin 
     if not RESTResponse.Status.Success then 
     begin 
     // do something ... 
     end else begin 
     // do something else ... 
     end; 
    end, 
    True 
); 
end; 
5

Mở rộng vào câu trả lời của Remy ...

"nhưng bây giờ vấn đề của tôi được rằng mã của tôi tiếp tục mà không chờ đợi phản ứng của restrequest"

Đây là mục đích chính xác của Async yêu cầu. Bạn không phải chờ đợi, bạn phải gửi yêu cầu và chuyển sang một thứ khác. Bạn cũng được yêu cầu cung cấp một thủ tục gọi lại nếu bạn muốn nắm bắt kết quả, nhưng tôi không thấy bạn làm điều đó trong mã của bạn.

Các cuộc gọi đồng bộ và không đồng bộ là các kiến ​​trúc thiết kế khá khác nhau, không thể đơn giản chuyển đổi giữa tầm thường như bạn mong muốn. Bạn không thể thay đổi Execute thành ExecuteAsync mà không cần phải thiết kế lại phần còn lại. Execute theo bản chất chờ phản hồi trong chuỗi cuộc gọi. Tuy nhiên, ExecuteAsync theo bản chất sinh ra yêu cầu trong một chuỗi mới, để mã của bạn có thể tiếp tục trong khi nó đang thực hiện công việc của bạn dưới nền. Không có chờ đợi liên quan đến yêu cầu Async - nó không tuân theo toàn bộ mục đích của chúng.

Tùy chọn lý tưởng nhất trong trường hợp của bạn là hủy bỏ một yêu cầu khỏi phản hồi của người kia. Có nghĩa là, chỉ gửi yêu cầu đầu tiên của bạn. Khi bạn nhận được phản hồi, sau đó gửi yêu cầu tiếp theo của bạn từ trong cuộc gọi trả lời của họ.

"... kể từ khi tôi muốn tạo ra các chức năng như một loại 'api gọi toàn cầu' chức năng để thực hiện mã hóa một chút ít ..."

Đó là hoàn toàn tốt đẹp. Bạn vẫn có thể có một thủ tục chính duy nhất thực hiện tất cả công việc. Đó là khi thực hiện từng cuộc gọi quan trọng ở đây. Sự kiện OnClick của nút không bao giờ là nơi thích hợp để thực hiện một điều như vậy. Điều duy nhất mà nút nên làm (vì nó nằm trong chuỗi giao diện người dùng chính) đang bắt đầu một yêu cầu. Nếu bạn muốn giao diện người dùng của bạn được đáp ứng ở tất cả sau thời điểm này, thì yêu cầu đó sẽ sinh ra một chuỗi mới theo một cách nào đó, hình dạng hoặc biểu mẫu.

Trong mọi trường hợp, WaitFor sẽ không cắt theo yêu cầu của bạn. Bạn muốn có một giao diện người dùng đáp ứng. WaitFor sẽ thách thức yêu cầu này và vẫn khóa luồng chính của ứng dụng của bạn.Toàn bộ mục đích của việc thực hiện công việc này trong một chủ đề khác là hiển thị hoạt ảnh "chờ đợi", sẽ không hoạt ảnh nếu chủ đề chính của bạn đang bận chờ đợi bất cứ điều gì.


EDIT

Tôi thực sự nghĩ đến một khả năng khác. Vẫn chỉ xuất hiện yêu cầu đầu tiên của bạn. Nhưng thay vì "chuỗi" một yêu cầu ra khỏi phản ứng của người khác, chỉ cần làm tất cả phần còn lại của công việc từ bên trong lời gọi lại của yêu cầu đầu tiên đó. Kể từ thời điểm này, nó đã có trong một chủ đề, không nhất thiết cần phải sinh ra một luồng khác cho yêu cầu tiếp theo. Chỉ cần thực hiện phần còn lại của những gì bạn cần sau yêu cầu đầu tiên, trong chuỗi hiện có bạn đã sinh ra.


Trên một mặt lưu ý, đây là một vấn đề rất phổ biến trong thế giới lập trình hiện đại ...

I Hate Programming

+1

Nice câu trả lời, ngoại trừ "một cách mù quáng" .. –

+0

@ John tôi có thể thấy sự nhầm lẫn - tôi chỉ có nghĩa là "mù quáng" như trong việc không ngay lập tức xem kết quả. Thay vào đó, một chuỗi mới hoạt động như một "người" khác để "thông báo" cho bạn về "những thay đổi" trong "chuỗi" ... Nói cách khác, một chuỗi khác chịu trách nhiệm thực hiện công việc và chuỗi cuộc gọi không cần phải quan tâm nhiều về các chi tiết chính xác. Họ chỉ muốn kết quả. Hãy nghĩ đến việc sinh ra một chủ đề mới, ví dụ, thuê một nhân viên mới có thể thực hiện một công việc cho bạn. Bạn cung cấp cho họ một nhiệm vụ, và bạn để cho họ đi theo cách của họ để làm nhiệm vụ đó, trong khi bạn tiếp tục với những thứ khác. –

+1

Tôi biết những gì một cách mù quáng có nghĩa là, và nó là một sử dụng kém ở đây .. Tôi vẫn +1 câu trả lời của bạn. –