我需要一个简单的
具体而言,我遇到的问题如下:
我可以写出以下代码:
请注意使用LazyThreadSafetyMode.PublicationOnly。
该代码在第一次调用时失败,对属性的后续调用被缓存(因此永远无法恢复)。
这在某种程度上是有道理的,因为我怀疑异常实际上从任务中没有传播出来,但我不能确定通知
有人能提供任何意见吗?
编辑:
感谢您的答案Ivan。我已经通过您的反馈成功地得到了一个基本的示例,但事实证明我的问题实际上比上面的基本示例更复杂,毫无疑问,这个问题会影响到其他人。
因此,如果我将属性签名更改为像这样的内容(根据Ivan的建议),则:
懒惰缓存bug再次出现,问题可以重现。
以下是一个完整的示例以重现此问题:
AsyncLazy<T>
,正好像Lazy<T>
一样,但能正确地处理异常并避免缓存它们。具体而言,我遇到的问题如下:
我可以写出以下代码:
public class TestClass
{
private int i = 0;
public TestClass()
{
this.LazyProperty = new Lazy<string>(() =>
{
if (i == 0)
throw new Exception("My exception");
return "Hello World";
}, LazyThreadSafetyMode.PublicationOnly);
}
public void DoSomething()
{
try
{
var res = this.LazyProperty.Value;
Console.WriteLine(res);
//Never gets here
}
catch { }
i++;
try
{
var res1 = this.LazyProperty.Value;
Console.WriteLine(res1);
//Hello World
}
catch { }
}
public Lazy<string> LazyProperty { get; }
}
请注意使用LazyThreadSafetyMode.PublicationOnly。
如果初始化方法在任何线程上抛出异常,则该异常会在该线程中传播到Value属性之外。 异常不会被缓存。
然后我以以下方式调用它。
TestClass _testClass = new TestClass();
_testClass.DoSomething();
它的运行方式与您预期的完全相同,当出现异常时,第一个结果被省略,结果保持未缓存状态,并且尝试读取该值的后续操作成功地返回“Hello World”。
不幸的是,如果我将我的代码更改为以下内容:
public Lazy<Task<string>> AsyncLazyProperty { get; } = new Lazy<Task<string>>(async () =>
{
if (i == 0)
throw new Exception("My exception");
return await Task.FromResult("Hello World");
}, LazyThreadSafetyMode.PublicationOnly);
该代码在第一次调用时失败,对属性的后续调用被缓存(因此永远无法恢复)。
这在某种程度上是有道理的,因为我怀疑异常实际上从任务中没有传播出来,但我不能确定通知
Lazy<T>
任务/对象初始化已经失败且不应被缓存的方法。有人能提供任何意见吗?
编辑:
感谢您的答案Ivan。我已经通过您的反馈成功地得到了一个基本的示例,但事实证明我的问题实际上比上面的基本示例更复杂,毫无疑问,这个问题会影响到其他人。
因此,如果我将属性签名更改为像这样的内容(根据Ivan的建议),则:
this.LazyProperty = new Lazy<Task<string>>(() =>
{
if (i == 0)
throw new NotImplementedException();
return DoLazyAsync();
}, LazyThreadSafetyMode.PublicationOnly);
然后像这样调用它。
await this.LazyProperty.Value;
代码能够正常运行。
但是如果你有以下这种方法:
this.LazyProperty = new Lazy<Task<string>>(() =>
{
return ExecuteAuthenticationAsync();
}, LazyThreadSafetyMode.PublicationOnly);
然后该方法会调用另一个异步方法。
private static async Task<AccessTokenModel> ExecuteAuthenticationAsync()
{
var response = await AuthExtensions.AuthenticateAsync();
if (!response.Success)
throw new Exception($"Could not authenticate {response.Error}");
return response.Token;
}
懒惰缓存bug再次出现,问题可以重现。
以下是一个完整的示例以重现此问题:
this.AccessToken = new Lazy<Task<string>>(() =>
{
return OuterFunctionAsync(counter);
}, LazyThreadSafetyMode.PublicationOnly);
public Lazy<Task<string>> AccessToken { get; private set; }
private static async Task<bool> InnerFunctionAsync(int counter)
{
await Task.Delay(1000);
if (counter == 0)
throw new InvalidOperationException();
return false;
}
private static async Task<string> OuterFunctionAsync(int counter)
{
bool res = await InnerFunctionAsync(counter);
await Task.Delay(1000);
return "12345";
}
try
{
var r = await this.AccessToken.Value;
}
catch (Exception ex) { }
counter++;
try
{
//Retry is never performed, cached task returned.
var r1 = await this.AccessToken.Value;
}
catch (Exception ex) { }
async
函数,而父函数GetNumbersAsync
没有async
关键字 - 这将停止在GetNumbersAsync
处的缓存,其中异常将被允许冒泡。您需要使用C# 7才能使用嵌套函数。 - ColinM