简体   繁体   English

RestSharp - 异步请求回复模式

[英]RestSharp - Asynchronous Request Reply Pattern

The following situation is given:给出以下情况:

  1. A new job is sent to an API via Post Request.新作业通过 Post Request 发送到 API。 This API returns a JobID and the HTTP ResponseCode 202.此 API 返回 JobID 和 HTTP ResponseCode 202。

  2. This JobID is then used to request a status endpoint.然后使用此 JobID 请求状态端点。 If the end point has a "Finished" property set in the response body, you can continue with step 3.如果端点在响应正文中设置了“已完成”属性,则可以继续执行第 3 步。

  3. The results are queried via a result endpoint using the JobID and can be processed.结果通过使用 JobID 的结果端点进行查询,并且可以进行处理。

My question is how I can solve this elegantly and cleanly.我的问题是如何优雅而干净地解决这个问题。 Are there perhaps already ready-to-use libraries that implement exactly this functionality?也许已经有现成的库可以实现这个功能? I could not find such functionality for RestSharp or another HttpClient.我找不到 RestSharp 或其他 HttpClient 的此类功能。 The current solution looks like this:当前的解决方案如下所示:

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

}

As @Paulo Morgado said, I should not use Thread.Sleep / Task Delay in production code.正如@Paulo Morgado 所说,我不应该在生产代码中使用 Thread.Sleep / Task Delay。 But in my opinion I have to use it in the method WaitTillJobIsReady() ?但在我看来,我必须在方法 WaitTillJobIsReady() 中使用它? Otherwise I would overwhelm the API with Get Requests in the loop?否则我会在循环中用 Get Requests 压倒 API?

What is the best practice for this type of problem?此类问题的最佳实践是什么?

Long Polling长轮询

There are multiple ways you can handle this type of problem, but as others have already pointed out no library such as RestSharp currently has this built in. In my opinion, the preferred way of overcoming this would be to modify the API to support some type of long-polling like Nikita suggested.有多种方法可以处理此类问题,但正如其他人已经指出的那样,目前没有任何库(例如 RestSharp)内置此功能。在我看来,克服此问题的首选方法是修改 API 以支持某种类型像 Nikita 建议的长轮询 This is where:这是哪里:

The server holds the request open until new data is available.服务器将请求保持打开状态,直到有新数据可用。 Once available, the server responds and sends the new information.一旦可用,服务器就会响应并发送新信息。 When the client receives the new information, it immediately sends another request, and the operation is repeated.当客户端收到新信息时,它立即发送另一个请求,并重复该操作。 This effectively emulates a server push feature.这有效地模拟了服务器推送功能。

Using a scheduler使用调度程序

Unfortunately this isn't always possible.不幸的是,这并不总是可能的。 Another more elegant solution would be to create a service that checks the status, and then using a scheduler such as Quartz.NET or HangFire to schedule the service at reoccurring intervals such as 500ms to 3s until it is successful.另一个更优雅的解决方案是创建一个检查状态的服务,然后使用诸如Quartz.NETHangFire 之类的调度程序以重复出现的间隔(例如 500 毫秒到 3 秒)来调度该服务,直到它成功。 Once it gets back the "Finished" property you can then mark the task as complete to stop the process from continuing to poll.一旦它返回“已完成”属性,您就可以将任务标记为已完成以停止进程继续轮询。 This would arguably be better than your current solution and offer much more control and feedback over whats going on.这可以说比您当前的解决方案更好,并且可以对正在发生的事情提供更多的控制和反馈。

Using Timers使用定时器

Aside from using Thread.Sleep a better choice would be to use a Timer .除了使用 Thread.Sleep 更好的选择是使用Timer This would allow you to continuously call a delegate at specified intervals, which seems to be what you are wanting to do here.这将允许您以指定的时间间隔连续调用委托,这似乎是您在这里想要做的。

Below is an example usage of a timer that will run every 2 seconds until it hits 10 runs.以下是计时器的示例用法,该计时器每 2 秒运行一次,直到运行 10 次。 (Taken from the Microsoft documentation ) (取自微软文档

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;
    }
}

Why you don't want to use Thread.Sleep为什么你不想使用 Thread.Sleep

The reason that you don't want to use Thread.Sleep for operations that you want on a reoccurring schedule is because Thread.Sleep actually relinquishes control and ultimately when it regains control is not up to the thread.您不想将 Thread.Sleep 用于需要重复发生的操作的原因是因为 Thread.Sleep 实际上放弃了控制权,最终何时重新获得控制权取决于线程。 It's simply saying it wants to relinquish control of it's remaining time for a least x milliseconds, but in reality it could take much longer for it to regain it.这只是说它想在至少 x 毫秒内放弃对剩余时间的控制,但实际上它可能需要更长的时间才能重新获得它。

Per the Microsoft documentation :根据Microsoft 文档

The system clock ticks at a specific rate called the clock resolution.系统时钟以称为时钟分辨率的特定速率滴答。 The actual timeout might not be exactly the specified timeout, because the specified timeout will be adjusted to coincide with clock ticks.实际超时可能不完全是指定的超时,因为将调整指定的超时以与时钟滴答一致。 For more information on clock resolution and the waiting time, see the Sleep function from the Windows system APIs.有关时钟分辨率和等待时间的更多信息,请参阅 Windows 系统 API 中的睡眠功能。

Peter Ritchie actually wrote an entire blog post on why you shouldn't use Thread.Sleep . Peter Ritchie 实际上写了一篇关于为什么不应该使用 Thread.Sleep 的博文

EndNote尾注

Overall I would say your current approach has the appropriate idea on how this should be handled however, you may want to 'future proof' it by doing some refactoring to utilize on of the methods mentioned above.总的来说,我会说您当前的方法对如何处理此问题有适当的想法,但是您可能希望通过进行一些重构以利用上述方法来“未来证明”它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM