Jim*_*hel 5 c# multithreading dispose race-condition
我有一个搜索应用程序需要一些时间(10到15秒)来返回一些请求的结果.对同一信息进行多个并发请求并不罕见.就目前而言,我必须独立处理这些,这需要相当多的不必要的处理.
我想出了一个可以让我避免不必要处理的设计,但是有一个挥之不去的问题.
每个请求都有一个标识所请求数据的密钥.我维护一个请求字典,由请求密钥键入.请求对象具有一些状态信息,WaitHandle用于等待结果.
当客户端调用我的Search方法时,代码会检查字典以查看该密钥是否已存在请求.如果是这样,客户端就等了WaitHandle.如果不存在请求,我创建一个请求,将其添加到字典中,并发出异步调用以获取信息.同样,代码等待事件.
当异步进程获得结果时,它会更新请求对象,从字典中删除请求,然后发出事件信号.
一切都很好.除了我不知道何时处理请求对象.也就是说,由于我不知道最后一个客户端何时使用它,我无法呼叫Dispose它.我必须等待垃圾收集器来清理.
这是代码:
class SearchRequest: IDisposable
{
public readonly string RequestKey;
public string Results { get; set; }
public ManualResetEvent WaitEvent { get; private set; }
public SearchRequest(string key)
{
RequestKey = key;
WaitEvent = new ManualResetEvent(false);
}
public void Dispose()
{
WaitEvent.Dispose();
GC.SuppressFinalize(this);
}
}
ConcurrentDictionary<string, SearchRequest> Requests = new ConcurrentDictionary<string, SearchRequest>();
string Search(string key)
{
SearchRequest req;
bool addedNew = false;
req = Requests.GetOrAdd(key, (s) =>
{
// Create a new request.
var r = new SearchRequest(s);
Console.WriteLine("Added new request with key {0}", key);
addedNew = true;
return r;
});
if (addedNew)
{
// A new request was created.
// Start a search.
ThreadPool.QueueUserWorkItem((obj) =>
{
// Get the results
req.Results = DoSearch(req.RequestKey); // DoSearch takes several seconds
// Remove the request from the pending list
SearchRequest trash;
Requests.TryRemove(req.RequestKey, out trash);
// And signal that the request is finished
req.WaitEvent.Set();
});
}
Console.WriteLine("Waiting for results from request with key {0}", key);
req.WaitEvent.WaitOne();
return req.Results;
}
Run Code Online (Sandbox Code Playgroud)
基本上,我不知道什么时候会发布最后一个客户端.无论我如何在这里切片,我都有竞争条件.考虑:
WaitOne,释放,并返回结果.如果我使用某种引用计数以便"最后"客户端调用Dispose,那么该对象将由线程A在上述场景中处理.线程C在试图等待处理时会死亡WaitHandle.
我能看到修复此问题的唯一方法是使用引用计数方案并使用锁保护对字典的访问(在这种情况下使用ConcurrentDictionary是无意义的),以便查找始终伴随引用计数的增量.虽然这会起作用,但它似乎是一个丑陋的黑客.
另一个解决方案是抛弃WaitHandle并使用类似事件的机制进行回调.但是,这也需要我用锁来保护查找,并且我还有处理事件或裸组播委托的复杂性.这似乎也是一种黑客行为.
这可能目前不是问题,因为此应用程序尚未获得足够的流量,以便在下一次GC传递到来之前将这些废弃的句柄添加起来并清除它们.也许它永远不会成为一个问题?但是,让我担心的是,当我打电话Dispose去除它们时,我要让GC清理它们.
想法?这是一个潜在的问题吗?如果是这样,你有一个干净的解决方案吗?
考虑使用Lazy<T>也许SearchRequest.Results?但这可能需要一些重新设计。还没有完全想清楚这一点。
但是,几乎可以直接替代您的用例的Wait()是Set()在SearchRequest. 就像是:
object _resultLock;
void Wait()
{
lock(_resultLock)
{
while (!_hasResult)
Monitor.Wait(_resultLock);
}
}
void Set(string results)
{
lock(_resultLock)
{
Results = results;
_hasResult = true;
Monitor.PulseAll(_resultLock);
}
}
Run Code Online (Sandbox Code Playgroud)
无需处置。:)