【发布时间】:2021-12-06 16:21:57
【问题描述】:
我有一个带有多线程测试的测试自动化环境,它使用共享的HttpClient 来测试我们 Web API 上的方法。在HttpClient 被初始化之后,它可以被我们在多个线程上运行的所有测试使用,因为它是一个线程安全的对象。然而,防止初始化不止一次发生是一个挑战。此外,它包含了 await 关键字,因此它不能使用任何基本的锁技术来确保初始化操作是原子的。
为了确保初始化正确进行,我使用SemaphoreSlim 来创建一个用于初始化的互斥锁。要访问该对象,所有测试都必须调用一个使用 SemaphoreSlim 的函数,以确保它已被第一个请求它的线程正确初始化。
我找到了在this web page 上使用SemaphoreSlim 的以下实现。
public class TimedLock
{
private readonly SemaphoreSlim toLock;
public TimedLock()
{
toLock = new SemaphoreSlim(1, 1);
}
public LockReleaser Lock(TimeSpan timeout)
{
if (toLock.Wait(timeout))
{
return new LockReleaser(toLock);
}
throw new TimeoutException();
}
public struct LockReleaser : IDisposable
{
private readonly SemaphoreSlim toRelease;
public LockReleaser(SemaphoreSlim toRelease)
{
this.toRelease = toRelease;
}
public void Dispose()
{
toRelease.Release();
}
}
}
我是这样使用这个类的:
private static HttpClient _client;
private static TimedLock _timedLock = new();
protected async Task<HttpClient> GetClient()
{
using (_timedLock.Lock(TimeSpan.FromSeconds(600)))
{
if (_client != null)
{
return _client;
}
MyWebApplicationFactory<Startup> factory = new();
_client = factory.CreateClient();
Request myRequest = new Request()
{
//.....Authentication code
};
HttpResponseMessage result = await _client.PostAsJsonAsync("api/accounts/Authenticate", myRequest);
result.EnsureSuccessStatusCode();
AuthenticateResponse Response = await result.Content.ReadAsAsync<AuthenticateResponse>();
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Response.Token);
return _client;
}
}
直到最近,当我在我的代码中添加了第九个线程时,它才能完美运行。我不得不将其拨回 8 个线程,因为每当我允许第 9 个线程调用 TimedLock.Lock 方法时,整个程序就会死锁。
有谁知道可能发生了什么,或者如何解决这个问题?
【问题讨论】:
-
会不会和出站连接限制有关? stackoverflow.com/questions/31735569/… 尝试提高到 10 以上。
-
IMO 这一切都太过分了。使用
Interlocked.Exchange并在返回值不为空时处理它 -
您能告诉我们您如何使用
SemaphoreSlim来保护HttpClient对象的初始化吗?如果您在没有linkedTimedLock类的帮助下使用SemaphoreSlim,您是否也测试过您的程序仍然死锁?此外,作为旁注,Lazy<HttpClient>实例看起来比使用SemaphoreSlim+code-found-on-the-web 更简单地解决此问题。 -
@TheodorZoulias 很好的问题。虽然我之前没有见过 Lazy ,但我怀疑它的问题将归结为初始化代码本身在其中等待的事实,因此不能保证同一个线程在启动后完成初始化。我将更新问题以至少包含说明该问题的伪代码。
-
约翰
using (_timedLock.Lock(...是一个阻塞调用,因为TimedLock调用Wait方法而不是正确的WaitAsync。这可能导致ThreadPool饥饿。如果您的机器有 8 个内核,那么ThreadPool以按需提供的 8 个线程开始。这条线也很可疑:Response = await result...。Response是什么?这是否意味着GetClient()有副作用?最后,异步初始化可以看this问题。
标签: c# multithreading deadlock semaphore