多个呼叫者等待长 运行 GET,有什么方法可以让他们都等待同一个 return?

Multiple callers waiting on long running GET, any way to make them all wait on the same return?

我是 运行 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() 。我怎样才能做到这一点,以便在字典为空时停止发出许多请求?

顺便说一句,假设此 List/Dictionary 将在服务存在的整个时间内保持填充状态是否安全?还是由于某种原因它会变空?处理这种情况的最佳方法是什么(我猜是其他一些缓存机制)?

谢谢!

如果不提供一种线程安全的机制来锁定小部件对象,直到第一个请求完成,这就不可能完全实现。由于 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 是否实际上仍然为空(之前的尝试可能已经加载它)。

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

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

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 会立即 return 当任务已经完成时,它是线程安全的;任务保证只执行一次。

用法:

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

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