2014-01-24 2 views
3

Я использую некоторые запросы REST, используя Mono.Mac (3.2.3) для связи с сервером, и в качестве механизма повтора я спокойно пытаюсь дать действиям 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() после его завершения первого (и последующий отказ). Есть ли простой способ сделать это, или я неправильно использую задачи таким образом? Есть ли какой-то метод, который сбрасывает задачу в исходное состояние, или мне лучше использовать какую-либо фабрику?

ответ

3

Вы действительно злоупотребляет Task здесь, по нескольким причинам:

  • Вы не можете выполнить ту же задачу несколько раз. Когда все будет сделано, все будет готово.

  • Не рекомендуется создавать объект Task вручную, для этого есть Task.Run10 и Task.Factory.Start.

  • Нельзя использовать Task.Run/Task.Factory.Start для выполнения задачи, выполняющей работу с IO-привязкой. Они предназначены для работы с процессором, поскольку они «занимают» поток от ThreadPool для выполнения действия задачи. Вместо этого используйте для этого чистые async Task API-интерфейсы, для которых не требуется выделенный поток.

Например, ниже вы можете позвонить 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.

+0

Это предполагает C# 5.0, мне непонятно, что это вариант. – svick

+0

@svick, то же самое можно сделать с помощью обратных вызовов 'ContinueWith', хотя код не будет таким читаемым. – Noseratio

+1

@svick, вот [версия .NET 4.0] (http://stackoverflow.com/a/21346870/1768303), как учебное упражнение для меня, и я научился любить «асинхронный/ждущий» еще больше. – Noseratio

0

Я рекомендовал бы делать что-то вроде этого:

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() 
{ 
    ... 
} 

Этот код из Retry Pattern Design от Microsoft. Вы можете это проверить здесь: https://msdn.microsoft.com/en-us/library/dn589788.aspx

Смежные вопросы