C# 我怎么知道它是什么时候';给他打电话安全吗?
我有一个搜索应用程序,它需要一些时间(10到15秒)来返回一些请求的结果。对同一信息有多个并发请求并不少见。就目前而言,我必须独立处理这些数据,这导致了相当多的不必要处理 我已经想出了一个设计,可以避免不必要的处理,但是有一个挥之不去的问题 每个请求都有一个密钥,用于标识所请求的数据。我维护一个请求字典,由请求键输入。请求对象有一些状态信息和用于等待结果的C# 我怎么知道它是什么时候';给他打电话安全吗?,c#,multithreading,dispose,race-condition,C#,Multithreading,Dispose,Race Condition,我有一个搜索应用程序,它需要一些时间(10到15秒)来返回一些请求的结果。对同一信息有多个并发请求并不少见。就目前而言,我必须独立处理这些数据,这导致了相当多的不必要处理 我已经想出了一个设计,可以避免不必要的处理,但是有一个挥之不去的问题 每个请求都有一个密钥,用于标识所请求的数据。我维护一个请求字典,由请求键输入。请求对象有一些状态信息和用于等待结果的WaitHandle 当客户端调用mySearch方法时,代码会检查字典,查看是否已经存在对该键的请求。如果是这样,客户端只需等待WaitHa
WaitHandle
当客户端调用mySearch
方法时,代码会检查字典,查看是否已经存在对该键的请求。如果是这样,客户端只需等待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;
}
类搜索请求:IDisposable
{
公共只读字符串请求密钥;
公共字符串结果{get;set;}
public ManualResetEvent WaitEvent{get;private set;}
公共搜索请求(字符串键)
{
RequestKey=key;
WaitEvent=新手动重置事件(false);
}
公共空间处置()
{
WaitEvent.Dispose();
总干事(本);
}
}
ConcurrentDictionary请求=新建ConcurrentDictionary();
字符串搜索(字符串键)
{
搜索请求请求;
bool addedNew=false;
req=请求。GetOrAdd(键)=>
{
//创建一个新请求。
var r=新的搜索请求;
WriteLine(“添加了带有{0}键的新请求”,key);
addedNew=真;
返回r;
});
如果(新增)
{
//创建了一个新请求。
//开始搜索。
ThreadPool.QueueUserWorkItem((obj)=>
{
//得到结果
req.Results=DoSearch(req.RequestKey);//DoSearch需要几秒钟
//从挂起列表中删除该请求
搜索请求垃圾;
Requests.TryRemove(req.RequestKey,out trash);
//并发出请求完成的信号
req.WaitEvent.Set();
});
}
WriteLine(“等待来自键为{0}的请求的结果”,键);
req.WaitEvent.WaitOne();
返回请求结果;
}
基本上,我不知道最后一个客户端何时发布。无论我在这里如何切分,我都有一个比赛条件。考虑:
Dispose
,那么在上述场景中,对象将由线程A处理。当线程C试图等待已释放的WaitHandle
时,它就会死亡
解决这个问题的唯一方法是使用引用计数方案,并使用锁保护对字典的访问(在这种情况下,使用concurrentdirectionary
是毫无意义的),以便查找总是伴随着引用计数的增加。尽管这会奏效,但它似乎是一个丑陋的黑客
另一个解决方案是抛弃WaitHandle
,使用类似事件的机制进行回调。但是,这也需要我用锁来保护查找,而且我还需要处理事件或裸体多播委托,这增加了复杂性。这看起来也像是一个黑客
这在当前可能不是问题,因为这个应用程序还没有获得足够的通信量,在下一个GC过程到来并清理它们之前,这些被丢弃的句柄无法累积。也许这永远不会是个问题?不过,这让我担心,当我应该调用Dispose
来清除它们时,我将它们留给GC清理
想法?这是一个潜在的问题吗?如果是这样,您是否有一个干净的解决方案?考虑使用Lazy
进行SearchRequest。结果可能是?但这可能需要一些重新设计。我还没想清楚
但是,对于您的用例来说,几乎是一个替代品,在SearchRequest
中实现您自己的Wait()
和Set()
方法。比如:
object _resultLock;
void Wait()
{
lock(_resultLock)
{
while (!_hasResult)
Monitor.Wait(_resultLock);
}
}
void Set(string results)
{
lock(_resultLock)
{
Results = results;
_hasResult = true;
Monitor.PulseAll(_resultLock);
}
}
无需处理。:) 我认为实现这一目标的最佳选择是使用TPL满足所有多线程需求。这就是它擅长的
根据我对你的问题的评论,你需要记住,concurrentdirectionary
确实有副作用。如果多个线程试图同时调用GetOrAdd
,则可以为所有线程调用工厂,但只有一个线程获胜。为其他线程生成的值将被丢弃,但此时计算已经完成
既然你也说过做搜索很昂贵,那么做一个锁定广告的成本
private Dictionary<string, Task<string>> _requests
= new Dictionary<string, Task<string>>();
public string Search(string key)
{
Task<string> task;
lock (_requests)
{
if (_requests.ContainsKey(key))
{
task = _requests[key];
}
else
{
task = Task<string>
.Factory
.StartNew(() => DoSearch(key));
_requests[key] = task;
task.ContinueWith(t =>
{
lock(_requests)
{
_requests.Remove(key);
}
});
}
}
return task.Result;
}