簡體   English   中英

RestSharp - 異步請求回復模式

[英]RestSharp - Asynchronous Request Reply Pattern

給出以下情況:

  1. 新作業通過 Post Request 發送到 API。 此 API 返回 JobID 和 HTTP ResponseCode 202。

  2. 然后使用此 JobID 請求狀態端點。 如果端點在響應正文中設置了“已完成”屬性,則可以繼續執行第 3 步。

  3. 結果通過使用 JobID 的結果端點進行查詢,並且可以進行處理。

我的問題是如何優雅而干凈地解決這個問題。 也許已經有現成的庫可以實現這個功能? 我找不到 RestSharp 或其他 HttpClient 的此類功能。 當前的解決方案如下所示:

async Task<string> PostNewJob()
{
  var restClient = new RestClient("https://baseUrl/");
  var restRequest = new RestRequest("jobs");        
  //add headers
           
  var response = await restClient.ExecutePostTaskAsync(restRequest);
  string jobId = JsonConvert.DeserializeObject<string>(response.Content);
  return jobId;
}

async Task WaitTillJobIsReady(string jobId)
{
  string jobStatus = string.Empty;
  var request= new RestRequest(jobId) { Method = Method.GET };  
  do
  {
    if (!String.IsNullOrEmpty(jobStatus))
        Thread.Sleep(5000); //wait for next status update

    var response = await restClient.ExecuteGetTaskAsync(request, CancellationToken.None);
    jobStatus = JsonConvert.DeserializeObject<string>(response.Content);
  } while (jobStatus != "finished");
}

async Task<List<dynamic>> GetJobResponse(string jobID)
{
  var restClient = new RestClient(@"Url/bulk/" + jobID);
  var restRequest = new RestRequest(){Method = Method.GET};
  var response =  await restClient.ExecuteGetTaskAsync(restRequest, CancellationToken.None);

  dynamic downloadResponse = JsonConvert.DeserializeObject(response.Content);
  var responseResult = new List<dynamic>() { downloadResponse?.ToList() };
  return responseResult;

}

async main()
{

var jobId = await PostNewJob();
WaitTillJobIsReady(jobID).Wait();
var responseResult = await GetJobResponse(jobID);

//handle result

}

正如@Paulo Morgado 所說,我不應該在生產代碼中使用 Thread.Sleep / Task Delay。 但在我看來,我必須在方法 WaitTillJobIsReady() 中使用它? 否則我會在循環中用 Get Requests 壓倒 API?

此類問題的最佳實踐是什么?

長輪詢

有多種方法可以處理此類問題,但正如其他人已經指出的那樣,目前沒有任何庫(例如 RestSharp)內置此功能。在我看來,克服此問題的首選方法是修改 API 以支持某種類型像 Nikita 建議的長輪詢 這是哪里:

服務器將請求保持打開狀態,直到有新數據可用。 一旦可用,服務器就會響應並發送新信息。 當客戶端收到新信息時,它立即發送另一個請求,並重復該操作。 這有效地模擬了服務器推送功能。

使用調度程序

不幸的是,這並不總是可能的。 另一個更優雅的解決方案是創建一個檢查狀態的服務,然后使用諸如Quartz.NETHangFire 之類的調度程序以重復出現的間隔(例如 500 毫秒到 3 秒)來調度該服務,直到它成功。 一旦它返回“已完成”屬性,您就可以將任務標記為已完成以停止進程繼續輪詢。 這可以說比您當前的解決方案更好,並且可以對正在發生的事情提供更多的控制和反饋。

使用定時器

除了使用 Thread.Sleep 更好的選擇是使用Timer 這將允許您以指定的時間間隔連續調用委托,這似乎是您在這里想要做的。

以下是計時器的示例用法,該計時器每 2 秒運行一次,直到運行 10 次。 (取自微軟文檔

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static Timer timer;

    static void Main(string[] args)
    {
        var timerState = new TimerState { Counter = 0 };

        timer = new Timer(
            callback: new TimerCallback(TimerTask),
            state: timerState,
            dueTime: 1000,
            period: 2000);

        while (timerState.Counter <= 10)
        {
            Task.Delay(1000).Wait();
        }

        timer.Dispose();
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: done.");
    }

    private static void TimerTask(object timerState)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: starting a new callback.");
        var state = timerState as TimerState;
        Interlocked.Increment(ref state.Counter);
    }

    class TimerState
    {
        public int Counter;
    }
}

為什么你不想使用 Thread.Sleep

您不想將 Thread.Sleep 用於需要重復發生的操作的原因是因為 Thread.Sleep 實際上放棄了控制權,最終何時重新獲得控制權取決於線程。 這只是說它想在至少 x 毫秒內放棄對剩余時間的控制,但實際上它可能需要更長的時間才能重新獲得它。

根據Microsoft 文檔

系統時鍾以稱為時鍾分辨率的特定速率滴答。 實際超時可能不完全是指定的超時,因為將調整指定的超時以與時鍾滴答一致。 有關時鍾分辨率和等待時間的更多信息,請參閱 Windows 系統 API 中的睡眠功能。

Peter Ritchie 實際上寫了一篇關於為什么不應該使用 Thread.Sleep 的博文

尾注

總的來說,我會說您當前的方法對如何處理此問題有適當的想法,但是您可能希望通過進行一些重構以利用上述方法來“未來證明”它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM