繁体   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