我有一个服务类,它确实请求一些外部服务。该外部服务需要sessionId,该sessionId的生存时间未知(可以是几秒钟,可以是几分钟,可以是几小时,可以是几天。可以是1个请求,可以是1000个请求。没人知道。我们不能链接到它)。
我创建了一个方法,该方法获取sessionId:
private async Task<string> LoginAsync()
{
using (HttpClient clientLogin = new HttpClient())
{
//....
}
return sessionId;
}
我在类中声明了以下变量
public class BillComService : IBillComService
{
private static Lazy<string> SessionId;
并在构造函数中将其初始化:
public BillComService()
{
if (SessionId == null)
SessionId = new Lazy<string>(() => LoginAsync().Result);
//....
}
然后在我的方法中使用它:
private async Task<T> Read<T>(string id, string endpoint)
{
var nvc = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("devKey", devKey),
new KeyValuePair<string, string>("sessionId", SessionId.Value)
};
好的,很好。
考虑一下sessionId是否过期。抛出BillComInvalidSessionException
现在,如果sessionId过期,我在调用LoginAsync
方法之后编写了以下方法来重复我的请求。
常用方法:
private async Task<T> Retry<T>(Func<Task<T>> func)
{
try
{
return await func();
}
catch (BillComInvalidSessionException)
{
SessionId = new Lazy<string>(() => LoginAsync().Result);
return await Retry(func);
}
}
和Read
:
private async Task<T> ReadWithRetry<T>(string id, string endpoint)
{
return await Retry(async () => await Read<T>(id, endpoint));
}
所以,我的公开方法是:
public async Task<Customer> GetCustomerAsync(string id)
{
return await ReadWithRetry<Customer>(id, "Crud/Read/Customer.json");
}
它正常工作。但是我不确定它是否是线程安全的:)
答案 0 :(得分:2)
不,它不是线程安全的。
当多个线程并行设置共享的SessionId
字段时发生竞争,而当一个线程设置SessionId
而其他线程正在使用它时,另一个竞争。
顺便说一句:似乎在调用LoginAsync()时缺少等待。
答案 1 :(得分:1)
您的代码不执行任何类型的同步,并且允许并行发出一个或多个登录。这是否是问题,完全取决于发出会话ID的远程实现。
这是我能想到的3种主要情况:
远程登录过程将同步每个开发人员密钥的会话ID生成,并确保为并发登录返回相同的会话ID。
即使在竞争情况下,远程登录过程也会在每次登录时发布一个新的会话ID,但是所有会话ID仍然有效。
远程登录过程将为每次登录发布一个新的会话ID,但随后的每次登录都会使给定开发人员密钥的先前发布的会话ID无效。
在情况1和2中,可能发生的最坏情况是由于并发登录过程导致效率低下(例如,不必要的网络IO)。
但是,在方案3中,事情可能变得很丑陋,因为您可能会遇到许多重试循环,因为会话ID可能在发出后立即失效。会话无效的并发请求越多,会话越差。
如果要确保代码安全而不依赖于远程实现,则必须同步SessionId
读写,这还涉及同步登录过程。尽管如此,远程实施行为仍是其服务合同的一部分,因此对于您的实施而言,考虑该行为也是合理的。