C#:此方法是否是线程安全的

时间:2019-02-02 05:37:01

标签: c# multithreading design-patterns thread-safety

我有一个服务类,它确实请求一些外部服务。该外部服务需要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");
    }

它正常工作。但是我不确定它是否是线程安全的:)

2 个答案:

答案 0 :(得分:2)

不,它不是线程安全的。

当多个线程并行设置共享的SessionId字段时发生竞争,而当一个线程设置SessionId而其他线程正在使用它时,另一个竞争。

顺便说一句:似乎在调用LoginAsync()时缺少等待。

答案 1 :(得分:1)

您的代码不执行任何类型的同步,并且允许并行发出一个或多个登录。这是否是问题,完全取决于发出会话ID的远程实现。

这是我能想到的3种主要情况:

  1. 远程登录过程将同步每个开发人员密钥的会话ID生成,并确保为并发登录返回相同的会话ID。

  2. 即使在竞争情况下,远程登录过程也会在每次登录时发布一个新的会话ID,但是所有会话ID仍然有效。

  3. 远程登录过程将为每次登录发布一个新的会话ID,但随后的每次登录都会使给定开发人员密钥的先前发布的会话ID无效。

在情况1和2中,可能发生的最坏情况是由于并发登录过程导致效率低下(例如,不必要的网络IO)。

但是,在方案3中,事情可能变得很丑陋,因为您可能会遇到许多重试循环,因为会话ID可能在发出后立即失效。会话无效的并发请求越多,会话越差。

如果要确保代码安全而不依赖于远程实现,则必须同步SessionId读写,这还涉及同步登录过程。尽管如此,远程实施行为仍是其服务合同的一部分,因此对于您的实施而言,考虑该行为也是合理的。