Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我怎么知道它是什么时候';给他打电话安全吗?_C#_Multithreading_Dispose_Race Condition - Fatal编程技术网

C# 我怎么知道它是什么时候';给他打电话安全吗?

C# 我怎么知道它是什么时候';给他打电话安全吗?,c#,multithreading,dispose,race-condition,C#,Multithreading,Dispose,Race Condition,我有一个搜索应用程序,它需要一些时间(10到15秒)来返回一些请求的结果。对同一信息有多个并发请求并不少见。就目前而言,我必须独立处理这些数据,这导致了相当多的不必要处理 我已经想出了一个设计,可以避免不必要的处理,但是有一个挥之不去的问题 每个请求都有一个密钥,用于标识所请求的数据。我维护一个请求字典,由请求键输入。请求对象有一些状态信息和用于等待结果的WaitHandle 当客户端调用mySearch方法时,代码会检查字典,查看是否已经存在对该键的请求。如果是这样,客户端只需等待WaitHa

我有一个搜索应用程序,它需要一些时间(10到15秒)来返回一些请求的结果。对同一信息有多个并发请求并不少见。就目前而言,我必须独立处理这些数据,这导致了相当多的不必要处理

我已经想出了一个设计,可以避免不必要的处理,但是有一个挥之不去的问题

每个请求都有一个密钥,用于标识所请求的数据。我维护一个请求字典,由请求键输入。请求对象有一些状态信息和用于等待结果的
WaitHandle

当客户端调用my
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;
}
类搜索请求: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();
返回请求结果;
}
基本上,我不知道最后一个客户端何时发布。无论我在这里如何切分,我都有一个比赛条件。考虑:

  • 线程A创建一个新请求,启动线程2,并等待等待句柄
  • 线程B开始处理请求
  • 线程C检测到有一个挂起的请求,然后被调出
  • 线程B完成请求,从字典中删除该项,并设置事件
  • 线程A的等待得到满足,并返回结果
  • 线程C被唤醒,调用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;
    }