【问题标题】:Restarting a task in the background if certain errors occur如果发生某些错误,则在后台重新启动任务
【发布时间】:2014-01-24 04:41:07
【问题描述】:

我正在使用 Mono.Mac (3.2.3) 的一些 REST 请求与服务器通信,作为重试机制,我正在悄悄地尝试在 HTTP 操作失败或超时时对其进行多次尝试。

我有以下;

var tries = 0;
while (tries <= ALLOWED_TRIES)
{
    try
    {
        postTask.Start();
        tries++;
        if (!postTask.Wait(Timeout))
        {
            throw new TimeoutException("Operation timed out");
        }
        break;
    } catch (Exception e) {
        if (tries > ALLOWED_TRIES)
        {
            throw new Exception("Failed to access Resource.", e);
        }
    }
}

任务使用父方法的参数的地方像这样;

var postTask = new Task<HttpWebResponse>(() => {return someStuff(foo, bar);},
    Task.Factory.CancellationToken, 
    Task.Factory.CreationOptions);

问题似乎是该任务不想在第一次完成(以及随后的失败)后使用postTask.Start() 再次运行。有没有一种简单的方法可以做到这一点,还是我以这种方式滥用任务?是否有某种方法可以将任务重置为其初始状态,还是我最好使用某种工厂?

【问题讨论】:

    标签: c# multithreading task-parallel-library


    【解决方案1】:

    我建议这样做:

    private int retryCount = 3;
    ...
    
    public async Task OperationWithBasicRetryAsync()
    {
      int currentRetry = 0;
    
      for (; ;)
      {
        try
        {
          // Calling external service.
          await TransientOperationAsync();
    
          // Return or break.
          break;
        }
        catch (Exception ex)
        {
          Trace.TraceError("Operation Exception");
    
          currentRetry++;
    
          // Check if the exception thrown was a transient exception
          // based on the logic in the error detection strategy.
          // Determine whether to retry the operation, as well as how 
          // long to wait, based on the retry strategy.
          if (currentRetry > this.retryCount || !IsTransient(ex))
          {
            // If this is not a transient error 
            // or we should not retry re-throw the exception. 
            throw;
          }
        }
    
        // Wait to retry the operation.
        // Consider calculating an exponential delay here and 
        // using a strategy best suited for the operation and fault.
        Await.Task.Delay();
      }
    }
    
    // Async method that wraps a call to a remote service (details not shown).
    private async Task TransientOperationAsync()
    {
      ...
    }
    

    此代码来自 Microsoft 的重试模式设计。你可以在这里查看:https://msdn.microsoft.com/en-us/library/dn589788.aspx

    【讨论】:

      【解决方案2】:

      您确实在这里误用了Task,原因如下:

      • 您不能多次运行同一任务。完成后,就完成了。

      • 不建议手动构造Task对象,有Task.RunTask.Factory.Start

      • 您不应该将Task.Run/Task.Factory.Start 用于执行IO 绑定工作的任务。它们用于受 CPU 限制的工作,因为它们从ThreadPool“借用”一个线程来执行任务操作。相反,为此使用纯异步Task-based APIs,不需要专用线程来完成。

      例如,您可以在下面从 UI 线程调用 GetResponseWithRetryAsync 并保持 UI 响应:

      async Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries)
      {
          if (retries < 0)
              throw new ArgumentOutOfRangeException();
      
          var request = WebRequest.Create(url);
          while (true)
          {
              try
              {
                  var result = await request.GetResponseAsync();
                  return (HttpWebResponse)result;
              }
              catch (Exception ex)
              {
                  if (--retries == 0)
                      throw; // rethrow last error
                  // otherwise, log the error and retry
                  Debug.Print("Retrying after error: " + ex.Message);
              }
          }
      }
      

      更多阅读:

      "Task.Factory.StartNew" vs "new Task(...).Start".

      Task.Run vs Task.Factory.StartNew.

      【讨论】:

      • 这假设 C# 5.0,我不清楚这是一个选项。
      • @svick,ContinueWith 回调也可以做到这一点,尽管代码不那么可读。
      • @svick,这是一个.NET 4.0 version,作为我的学习练习,我学会了更爱async/await
      • 为了提示和“理论”组件,我将把它作为一个答案,但应该注意的是扩展问题(兼容 .NET 4)中提供的答案是我的派生解决方案是有源的。感谢@Noseratio 的努力,非常感谢。
      • @DanielPark,很高兴它有帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多