多个呼叫者等待长时间运行的GET,任何方式让他们都等待同样的回报?

时间:2015-04-09 23:24:01

标签: c# wcf rest

我正在运行WCF REST服务,并发现了一些瓶颈。基本上,当前的代码归结为以下内容:

private static List<Widget> widgets;  

public async Task<List<SearchResult>> Search(string term)
{
    if(widgets == null) {
        // This call takes up to 60 seconds
        widgets = await GetWidgets();
    }
    return SearchUtil.Search(term, widgets);
}

问题是许多请求可以进入if检查并调用这个长时间运行的操作。相反,我希望任何其他传入请求基本上等待原始调用完成,并且只进行一次GetWidgets()调用。如何实现这一点,以便在字典为空时停止发出许多请求?

作为一个小小的一边,假设这个列表/词典在整个服务生存期间保持填充是否安全?或者它可能由于某种原因而空了?处理这种情况的最佳方法是什么(我猜其他一些缓存机制)?

谢谢!

2 个答案:

答案 0 :(得分:1)

如果没有提供一个线程安全的机制来锁定小部件对象直到第一个请求完成,那么这是完全不可能的。由于WCF服务是异步的,许多请求可以立即开始调用widgets = await GetWidgets();方法。

我建议简单锁定,例如:

private static List<Widget> widgets;  
static readonly object _widgetsLock = new object();

public async Task<List<SearchResult>> Search(string term)
{
    if(widgets == null) {
        // This call takes up to 60 seconds
        lock(_widgetsLock)
             if(widgets == null)
                  widgets = await GetWidgets();
    }
    return SearchUtil.Search(term, widgets);
}

只需锁定_widgetsLock对象,该对象将通过锁定方法停止所有其他执行,直到lock被释放。现在一旦锁定被释放,如果有一个等待线程来访问该代码,它会双重检查widgets是否仍为空(之前的尝试可能已经加载了它)。

您还可以在应用程序启动处理请求之前在应用程序启动时加载窗口小部件。

答案 1 :(得分:1)

如果我理解正确,您需要一个永不过期的缓存,并在第一次有人请求该值时填充。以通用方式构建应该非常简单:

class NonExpiringLazyLoadingCache<T>
{
    private readonly Func<Task<T>> _factory;
    private Task<T> _retrievalTask;
    private readonly object _lockObject = new object();

    public NonExpiringLazyLoadingCache(Func<Task<T>> factory)
    {
        this._factory = factory;
    }

    public async Task<T> GetValue()
    {
        lock (this._lockObject)
            if (this._retrievalTask == null)
                this._retrievalTask = this._factory();

        await this._retrievalTask;
        return this._retrievalTask.Result;
    }
}

需要注意的一点是Task<T> await会在任务完成后立即返回,它是线程安全的;保证任务只执行一次。

用法:

 private static NonExpiringLazyLoadingCache<List<Widget>> cache = new NonExpiringLazyLoadingCache<List<Widget>>(GetWidgets);
 ...
 var widgets = await cache.GetValue();

对于对象生命周期的持续时间,它取决于托管WCF服务的位置。只要进程保持活动状态,它通常会保留,对于IIS托管服务,当应用程序池被回收时,该值将被清除。