C# 并发字典 介绍

C# 并发字典 介绍,c#,concurrency,dictionary,C#,Concurrency,Dictionary,我正在为 目前,我正在实现一个缓存特性,直到现在这还不是一个问题。现在我要考虑并发性。这将是我的测试方法: 代码 描述 实际执行请求的代码与此问题无关。尝试访问缓存的代码执行以下操作: 代码 问题是不确定的,有时两个请求都能通过,而不是像设计的那样只有一个 我在想,在访问过程中,某些非线程安全的东西正在被访问。但我不知道那可能是什么。它可能是什么,或者我应该如何正确地调试它 更新 问题是我在ConcurrentDictionary 此方法没有重新调整指示缓存是否已成功更新的布尔值,因此如果此方法

我正在为
目前,我正在实现一个缓存特性,直到现在这还不是一个问题。现在我要考虑并发性。这将是我的测试方法:

代码 描述 实际执行请求的代码与此问题无关。尝试访问缓存的代码执行以下操作:

代码 问题是不确定的,有时两个请求都能通过,而不是像设计的那样只有一个

我在想,在访问过程中,某些非线程安全的东西正在被访问。但我不知道那可能是什么。它可能是什么,或者我应该如何正确地调试它

更新 问题是我在
ConcurrentDictionary

此方法没有重新调整指示缓存是否已成功更新的
布尔值,因此如果此方法失败,
Get()
将返回两次
null

代码
//
///尝试将API响应推送到缓存存储区。
/// 
///预期API结果的强类型。
///查询的API终结点。
///API响应。
///如果操作成功,则为True,否则为false。
公共bool Push(字符串端点,IApiResponse响应),其中T:class
{
if(endpoint.NullOrEmpty())
{
返回false;
}
IApiResponseCacheItem;
if(Cache.TryGetValue(端点,out项))
{
((IApiResponseCacheItem)项)。更新响应(响应);
返回true;
}
其他的
{
项目=新的ApiResponseCacheItem(响应);
返回Cache.TryAdd(端点,项);
}
}
描述 解决方案是实现返回值,并更改
Get()
添加以下内容:

代码
if(Push(endpoint,null)| | retries>1)//max retries for sanity。
{
返回null;
}
其他的
{
返回Get(端点++重试);//重试推送。
}
IApiResponseCacheItem cacheItem=Store.Get(端点);
if(cacheItem!=null)
{
//等等。。
}
ConcurrentDirectional是线程安全的,但这不会自动使代码线程安全。上面的代码片段是问题的核心。两个线程可以同时调用Get()方法并获得null。它们都将继续并同时调用PerformRequest()。您需要合并InternalProcessing()和FetchFromCache(),并确保只有一个线程可以使用锁调用PerformRequest。这可能会产生较差的并发性,也许您可以删除一个重复的响应。在所有的可能性中,请求无论如何都会被SE服务器序列化,因此这可能无关紧要。

IApiResponseCacheItem cacheItem=Store.get(endpoint);
if(cacheItem!=null)
{
//等等。。
}

ConcurrentDirectional是线程安全的,但这不会自动使代码线程安全。上面的代码片段是问题的核心。两个线程可以同时调用Get()方法并获得null。它们都将继续并同时调用PerformRequest()。您需要合并InternalProcessing()和FetchFromCache(),并确保只有一个线程可以使用锁调用PerformRequest。这可能会产生较差的并发性,也许您可以删除一个重复的响应。很可能,请求无论如何都会被SE服务器序列化,所以这可能无关紧要。

您显示的方法不安全,这导致您描述的。。。重入安全与线程安全不同…我如何将我的方法转变为您建议的重入安全方法?基本上,您需要实现一种方法来检测这种情况,并确定在发生重入时需要发生什么-暂停它直到当前运行完成,放弃它,并行处理。。。这些选项中的任何一个都可能有副作用-这同样取决于您的用例/目标,我现在还不太清楚这就是代码应该做的事情,在第一次运行时,它发出请求并发出信号,表示后续运行应等待请求完成。从我所看到的情况来看,您的方法没有检测到重入情况-至少不可靠。。。由于您不是在检查可重入性,而是在检查某些缓存项。。。检查可重入性就像一个“哨兵”在一个方法的条目上。。。您的检查没有考虑同一端点多次并行输入同一方法的情况…您显示的方法不安全,这导致您描述的结果。。。重入安全与线程安全不同…我如何将我的方法转变为您建议的重入安全方法?基本上,您需要实现一种方法来检测这种情况,并确定在发生重入时需要发生什么-暂停它直到当前运行完成,放弃它,并行处理。。。这些选项中的任何一个都可能有副作用-这同样取决于您的用例/目标,我现在还不太清楚这就是代码应该做的事情,在第一次运行时,它发出请求并发出信号,表示后续运行应等待请求完成。从我所看到的情况来看,您的方法没有检测到重入情况-至少不可靠。。。由于您不是在检查可重入性,而是在检查某些缓存项。。。检查可重入性就像一个“哨兵”在一个方法的条目上。。。您的检查没有考虑同一端点多次并行输入同一方法的情况。。。
public static void TestConcurrency()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    IList<Task> tasks = new List<Task>();
    for (int i = 0; i < 1000; i++)
    {
        tasks.Add(Task.Factory.StartNew(p => client.GetAnswers(), null));
    }
    Task.WaitAll(tasks.ToArray());
    sw.Stop();
    Console.WriteLine("elapsed: {0}", sw.Elapsed.ToString());
    Console.ReadKey();
}
/// <summary>
/// Checks the cache and then performs the actual request, if required.
/// </summary>
/// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam>
/// <param name="endpoint">The API endpoint to query.</param>
/// <returns>The API response object.</returns>
private IApiResponse<T> InternalProcessing<T>(string endpoint) where T : class
{
    IApiResponse<T> result = FetchFromCache<T>(endpoint);
    return result ?? PerformRequest<T>(endpoint);
}
/// <summary>
/// Attempts to fetch the response object from the cache instead of directly from the API.
/// </summary>
/// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam>
/// <param name="endpoint">The API endpoint to query.</param>
/// <returns>The API response object.</returns>
private IApiResponse<T> FetchFromCache<T>(string endpoint) where T : class
{
    IApiResponseCacheItem<T> cacheItem = Store.Get<T>(endpoint);
    if (cacheItem != null)
    {
        IApiResponse<T> result = cacheItem.Response;
        result.Source = ResultSourceEnum.Cache;
        return result;
    }
    return null;
}
/// <summary>
/// Attempts to access the internal cache and retrieve a response cache item without querying the API.
/// <para>If the endpoint is not present in the cache yet, null is returned, but the endpoint is added to the cache.</para>
/// <para>If the endpoint is present, it means the request is being processed. In this case we will wait on the processing to end before returning a result.</para>
/// </summary>
/// <typeparam name="T">The strong type of the expected API result.</typeparam>
/// <param name="endpoint">The API endpoint</param>
/// <returns>Returns an API response cache item if successful, null otherwise.</returns>
public IApiResponseCacheItem<T> Get<T>(string endpoint) where T : class
{
    IApiResponseCacheItem cacheItem;
    if (Cache.TryGetValue(endpoint, out cacheItem))
    {
        while (cacheItem.IsFresh && cacheItem.State == CacheItemStateEnum.Processing)
        {
            Thread.Sleep(10);
        }
        if (cacheItem.IsFresh && cacheItem.State == CacheItemStateEnum.Cached)
        {
            return (IApiResponseCacheItem<T>)cacheItem;
        }
        IApiResponseCacheItem value;
        Cache.TryRemove(endpoint, out value);
    }
    Push<T>(endpoint, null);
    return null;
}
/// <summary>
/// Attempts to push API responses into the cache store.
/// </summary>
/// <typeparam name="T">The strong type of the expected API result.</typeparam>
/// <param name="endpoint">The queried API endpoint.</param>
/// <param name="response">The API response.</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public bool Push<T>(string endpoint, IApiResponse<T> response) where T : class
{
    if (endpoint.NullOrEmpty())
    {
        return false;
    }
    IApiResponseCacheItem item;
    if (Cache.TryGetValue(endpoint, out item))
    {
        ((IApiResponseCacheItem<T>)item).UpdateResponse(response);
        return true;
    }
    else
    {
        item = new ApiResponseCacheItem<T>(response);
        return Cache.TryAdd(endpoint, item);
    }
}
if (Push<T>(endpoint, null) || retries > 1) // max retries for sanity.
{
    return null;
}
else
{
    return Get<T>(endpoint, ++retries); // retry push.
}
IApiResponseCacheItem<T> cacheItem = Store.Get<T>(endpoint);
if (cacheItem != null)
{
   // etc..
}