我想知道哪种方法在内存和资源使用方面更有效率。
特别是方法#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;
}
方法#2(忽略两者之间异常传播的差异):
internal static async Task ExecuteWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry)
{
while (true)
{
try
{
await methodToExecute();
}
catch(Exception ex)
{
if(!shouldRetry())
{
throw;
}
}
}
}
答案 0 :(得分:4)
除了不同的例外和取消传播之外,还有另一个主要区别。
在第一种情况下,由于TaskContinuationOptions.ExecuteSynchronously
,您的继续在任务完成的同一线程上运行。
在第二种情况下,它将在原始同步上下文上运行(如果在具有同步上下文的线程上调用methodToExecute
)。
即使第一种方法可能更有效,但也可能难以理解(特别是当您或其他人在一年内返回时)。
我会跟随KISS principle并坚持使用第二个修正案:
await methodToExecute().ConfigureAwait(false);
更新了以解决评论:
“OTOH,#1在概念上似乎是递归的,但它会在递归中递归 传统意义,如在一个堆栈框架中等待另一个?“
对于#1,无论是在同一堆栈帧上递归发生,还是在不同堆栈帧上异步发生,完全取决于methodToExecute
内部的内容。在大多数情况下,如果在methodToExecute
中使用一些自然异步API,则不会有传统的递归。例如,HttpClient.GetStringAsync
completes on a random IOCP pool thread和Task.Delay
在随机工作池线程上完成。
但是,即使异步API也可以在同一个线程上同步完成(例如MemoryStream.ReadAsync
或Task.Delay(0)
),在这种情况下会有递归。
或者,在TaskCompletionSource.SetResult
内使用methodToExecute
也可能会触发同步延续。
如果您真的想避免任何递归的可能性,请通过methodToExecute
(或Task.Run
/ Task.Factory.StartNew
)致电Task.Unwrap
。或者,更好的是,删除TaskContinuationOptions.ExecuteSynchronously
。
对于#2,即使初始线程上存在同步上下文,也可能出现相同的情况。