gpt4 book ai didi

c# - 调用异步委托(delegate)时防止 Lazy 缓存异常

转载 作者:太空狗 更新时间:2023-10-30 00:29:32 24 4
gpt4 key购买 nike

我需要一个简单的 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 的使用.

If the initialization method throws an exception on any thread, the exception is propagated out of the Value property on that thread. The exception is not cached.

然后我按以下方式调用它。

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> 的方法任务/对象初始化失败,不应缓存。

有人能提供任何意见吗?

编辑:

感谢伊万的回答。我已经成功地通过您的反馈获得了一个基本示例,但事实证明我的问题实际上比上面的基本示例更复杂,毫无疑问,这个问题会影响其他处于类似情况的人。

所以如果我将我的属性签名更改为这样的东西(根据 Ivans 的建议)

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);

然后它自己调用另一个 Async 方法。

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;
}

延迟缓存错误再次出现,问题可以重现。

这是重现问题的完整示例:

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

最佳答案

问题是如何Lazy<T>定义“失败”如何干扰 Task<T>定义“失败”。

对于 Lazy<T>初始化为“失败”,它必须引发异常。这是完全自然且可以接受的,尽管它是隐式同步的。

对于 Task<T>为了“失败”,异常被捕获并放置在任务中。这是异步代码的正常模式。

将两者结合起来会导致问题。 Lazy<T>的一部分 Lazy<Task<T>>只有在直接引发异常时才会“失败”,并且 async Task<T>的图案不直接传播异常。所以async工厂方法将始终(同步)“成功”,因为它们返回 Task<T>。 .此时Lazy<T>部分实际上已经完成;它的值已生成(即使 Task<T> 尚未完成)。

您可以构建自己的 AsyncLazy<T>键入没有太多麻烦。您不必只为那一种类型依赖 AsyncEx:

public sealed class AsyncLazy<T>
{
private readonly object _mutex;
private readonly Func<Task<T>> _factory;
private Lazy<Task<T>> _instance;

public AsyncLazy(Func<Task<T>> factory)
{
_mutex = new object();
_factory = RetryOnFailure(factory);
_instance = new Lazy<Task<T>>(_factory);
}

private Func<Task<T>> RetryOnFailure(Func<Task<T>> factory)
{
return async () =>
{
try
{
return await factory().ConfigureAwait(false);
}
catch
{
lock (_mutex)
{
_instance = new Lazy<Task<T>>(_factory);
}
throw;
}
};
}

public Task<T> Task
{
get
{
lock (_mutex)
return _instance.Value;
}
}

public TaskAwaiter<T> GetAwaiter()
{
return Task.GetAwaiter();
}

public ConfiguredTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
{
return Task.ConfigureAwait(continueOnCapturedContext);
}
}

关于c# - 调用异步委托(delegate)时防止 Lazy<T> 缓存异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56404540/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com