有没有办法在C#中同步访问单例?

问题描述 投票:0回答:1

我在尝试针对单例运行单元测试时遇到了一种情况。 我使用单例来管理单个 HttpClient,我的应用程序可以使用它来发出 Http 请求。我知道还有其他方法可以做到这一点,但这是我目前正在使用的方法。

在正常操作期间,这种方法没有问题,因为 HttpClient 对象仅创建一次,并且在应用程序的生命周期内永远不会更改。 当我尝试运行单元测试时,问题就出现了,因为我正在创建多个 HttpClient,以便我可以模拟它们的响应。

这基本上就是我的单例类的样子(这不是实际的代码,只是一个展示基本思想的示例)。

internal sealed class MySingleton
{
    private static readonly Lazy<MySingleton> _ instance = new Lazy<MySingleton>(() => new MySingleton());

    public static MySingleton Instance => _instance.Value;

    private HttpClient _httpClient;

    internal HttpClient HttpClient
    {
        get => _httpClient;
        set //This is provided for testing, so I can mock the HttpMessageHandler
        {
            _httpClient = value;
        }
    }

    private MySingleton()
    {
        SocketsHttpHandler
            handler = new SocketsHttpHandler
            {
                PooledConnectionLifetime = TimeSpan.FromMinutes(10)
            };
        _httpClient = new HttpClient(handler);
    }

    internal async Task<bool> SendRequest()
    {
        //Uses the HttpClient to send a request.
        return true;
    }
}

有了这个,每当我的应用程序需要发送 Http 请求时,它就可以调用

await MySingleton.Instance.Send()
。这在正常操作中工作得很好。

当我运行单元测试时,问题就出现了,因为它们操作 HttpClient 并并行运行。目前我正在强制测试同步运行,但这不是一个理想的解决方案。 这是我的测试的一个想法。

public class Tests
{
    [Fact]
    public async Task This_Is_A_Test()
    {
        string
            jsonText = """
            {
                "id" : 1,
                "name" : "testing",
                "value" : 23
            }
            """;
        HttpMessageHandler
            httpMessageHandler = new MockHttpMessageHandler(jsonText, System.Net.HttpStatus.Created);
        MySingleton.Instance.HttpClient = new HttpClient(httpMessageHandler);

        bool
            result = await MySingleton.Instance.SendRequest();
        Assert.IsTrue(result);
    }
}

MockHttpMessageHandler
对象只是我编写的一个扩展了
HttpMessageHandler
类的类,因此我可以操纵来自HttpClient的响应。

正如您从测试代码中看到的,我将单例中的 HttpClient 替换为将返回我想要的数据的 HttpClient。 当测试并行运行时,这会产生导致测试失败的副作用。

我想知道是否有一种方法可以同步对单例实例的访问,以便它一次只能被一个线程使用。基本上,一个线程会获取单例,将其用于所需的用途,然后将其释放回以供另一个线程使用。

也许我的做法是完全错误的,在这种情况下,我愿意接受建议。虽然我真的很想知道是否可以做我想做的事情。我试图用谷歌搜索答案,但只想到了同步实例的创建(Lazy 已经处理了)。我什至尝试过人工智能,尽管那没有任何帮助。

c# unit-testing mocking httpclient
1个回答
0
投票

您可以非常轻松地同步访问。一种方法是用执行同步的方法替换

HttpClient
属性。就像下面这样。

// this code replaces the HttpClient property
private HttpClient _httpClient;
private static object lockObj = new object();

// Attempts to acquire the object.
// If it can't, it returns null.
public static HttpClient TryGet()
{
    if (Monitor.TryEnter(lockObj))
       return HttpClient;
    return false;
}

// Attempts to acquire the object.
// Blocks until it can.
public static HttpClient Get()
{
    Monitor.Enter(lockObj);
    return HttpClient;
}

// Releases the previously-acquired object
public static Release()
{
    Monitor.Exit(lockObj);
}

要使用它,您可以调用

Get
TryGet
来获取对
HttpClient
的独占访问权限。完成后,请致电
Release
。比如:

var client = MySingleton.Get();
try
{
    // do whatever you need to do with client
}
finally
{
    MySingleton.Release();
}

适用标准注意事项。如果获取资源的线程终止而没有释放它,则可能会遇到问题。老实说,我不知道

Monitor
如何处理放弃。

© www.soinside.com 2019 - 2024. All rights reserved.