使用任务重试异步功能-哪种方法更有效?

Kak*_*ira 5 c# asynchronous task-parallel-library async-await

我想知道哪种方法通常在内存和资源使用方面会更有效。

尤其是方法1,我很难想象将如何创建任务对象和线程旋转?有人可以解释一下幕后发生的事情吗?

如果两者之间没有区别,我想使用#1(以免引起异步气泡)。对于#2,我知道编译器将在下面生成状态机并产生收益。OTOH,#1在概念上似乎是递归的,但是在传统意义上会像在一个堆栈帧中等待另一个一样递归吗?

方法1:

internal static Task ExecuteAsyncWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry)
    {
        var tcs = new TaskCompletionSource<object>();

        try
        {
            return methodToExecute().ContinueWith<Task>((t) =>
            {
                if (t.IsFaulted || t.IsCanceled)
                {
                    if (shouldRetry())
                    {
                        return ExecuteAsyncWithRetry(methodToExecute, shouldRetry);
                    }
                    else
                    {
                        tcs.SetException(t.Exception);
                    }
                }
                else
                {
                    tcs.SetResult(null);

                }

                return tcs.Task;
            }, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
        }
        catch(Exception ex)
        {
            tcs.SetException(ex);
        }

        return tcs.Task;
    }
Run Code Online (Sandbox Code Playgroud)

方法2(忽略两者之间异常传播的差异):

internal static async Task ExecuteWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry)
    {
        while (true)
        {
            try
            {
                await methodToExecute();
            }
            catch(Exception ex)
            {
                if(!shouldRetry())
                {
                    throw;
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

nos*_*tio 5

除了不同的异常和取消传播外,还有另一个主要区别。

在第一种情况下,由于,您的继续在任务已完成的同一线程上运行TaskContinuationOptions.ExecuteSynchronously

在第二种情况下,它将在原始同步上下文上运行(如果methodToExecute在具有同步上下文的线程上调用)。

即使第一种方法可能更有效,也可能很难理解(尤其是当您或其他人在一年后返回时)。

我将遵循KISS原则,并坚持第二项原则,但有一项修正:

await methodToExecute().ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

更新以解决评论:

“ OTOH,#1在概念上似乎是递归的,但是在传统意义上它会像在一个堆栈帧中等待另一个一样递归吗?”

对于#1,它是递归发生在同一堆栈框架上还是异步发生在不同的堆栈框架上,完全取决于内部情况methodToExecute。在大多数情况下,如果您在中使用一些自然的异步API,就不会有传统的递归methodToExecute。例如,HttpClient.GetStringAsync 在随机IOCP池线程上Task.Delay完成,并在随机工作池池线程上完成。

但是,即使是异步API也可能在同一线程(例如MemoryStream.ReadAsyncTask.Delay(0))上同步完成,在这种情况下,将存在递归。

或者,使用TaskCompletionSource.SetResult内部methodToExecute也可能触发同步延续。

如果您确实想避免任何递归的可能性,请methodToExecute通过Task.Run(或Task.Factory.StartNew/ Task.Unwrap)进行调用。或者,最好删除TaskContinuationOptions.ExecuteSynchronously

对于#2,即使初始线程上存在同步上下文,也可以使用相同的方案。