首页 > 解决方案 > 在无限循环中执行 await Task.Delay

问题描述

我有下面的代码,它将执行Action operation,如果其中发生异常,它将被重试。

因为代码await Task.Delay(delay);在里面while(true),我在犹豫这是否会导致任何内存泄漏?喜欢创造无限threads

这段代码工作正常,但我只担心可能的内存泄漏?如果有人可以分享一些见解,我将不胜感激。

public class OperationWithBasicRetry
{
  public async Task StartOperationAsync(Action operation, TimeSpan delay, int retryCount)
  {
    int currentRetry = 0;

    while(true)
    {
      try
      {
        operation();

        // Success
        break;
      }
      catch (Exception ex)
      {
        if (++currentRetry > retryCount)
        {
          throw;
        }
      }

      await Task.Delay(delay);
    }
  }
}

标签: c#memory-leaks

解决方案


这是安全的。

这是一种async方法,因此编译器将其分解并变成状态机。作为一个非常粗略的近似来说明这一点,您可以认为编译后的代码看起来有点像这样:

private class State
{
    public int currentRetry;
    public Action operation;
    public TimeSpan delay;
    public int retryCount;
    public TaskCompletionSource<object> tcs;

    private void StartOperationAsyncImpl(object unused)
    {
        try
        {
            operation();
            tcs.SetResult(null);
            return;
        }
        catch (Exception ex)
        {
            if (++currentRetry > retryCount)
            {
                tcs.SetException(ex);
            }
        }

        // I'm ignoring the delay bit, because it has no affect on the point
        // I'm trying to make.
        ThreadPool.QueueUserWorkItem(StateOperationAsyncImpl);
    }
}

public Task StartOperationAsync(Action operation, TimeSpan delay, int retryCount)
{
    State state = new State();
    state.currentRetry = 0;
    state.operation = operation;
    state.delay = delay;
    state.retryCount = retryCount;
    state.tcs = new TaskCompletionSource<object>();

    ThreadPool.QueueUserWorkItem(state.StartOperationAsyncImpl);

    return state.tcs;
}

当然,实际编译的代码看起来不像这样(看起来像这样),但它说明了我的观点。

这里没有递归。甚至没有无限循环。你有一个方法,当被调用时,它会尝试执行你的操作。如果失败,则将自己排队到 ThreadPool 中并返回。该调用立即ThreadPool.QueueUserWorkItem返回,并且不会等到工作完成。

ThreadPool.QueueUserWorkItem也不会创建新线程 - 它会将要由预先存在的线程池执行的工作排队。

(在有人评论之前 - 我知道实际编译的代码不会ThreadPool.QueueUserWorkItem直接使用,但它可能会使用默认的 TaskScheduler,它在ThreadPool.UnsafeQueueUserWorkItem内部调用,所以这是一个很好的近似值)。


推荐阅读