【问题标题】:Xamarin PCL - Making API calls based on user entry, how to dampen requests?Xamarin PCL - 根据用户输入进行 API 调用,如何抑制请求?
【发布时间】:2016-05-06 11:31:14
【问题描述】:

我有一个搜索任务,它在可移植类库中向 API 发出请求,当用户在文本框中输入文本时,这按预期工作,但我担心大规模性能。当我们拥有庞大的用户群时,每次按键都会向这个 API 发出请求,我可以预见性能问题。

我已将 API 调用限制为仅在有效字符超过三个时触发,但我想进一步抑制这种情况。我可以在此之上实现一个计时器,但它感觉不是一个好的解决方案,并且不存在于 PCL 框架中。

是否有推荐的模式来实现这种类型的请求抑制?

    private async Task GetClubs()
    {
        try
        {
            if (!string.IsNullOrWhiteSpace(ClubSearch) && ClubSearch.Replace(" ", "").Length >= 3)
            {
                Clubs = await dataService.GetClubs(ClubSearch);
            }
        }
        catch (DataServiceException ex)
        {
            ...
        }
    }

【问题讨论】:

  • 为什么定时器感觉不好?
  • @Evk 定时器的实现对我来说有点 hacky。这是一个没有实际意义的话题,因为我需要它在不包含计时器实现的可移植类库中工作。我将更新我的问题以反映这一点。
  • 而且至少有Task.Delay可用?
  • @Evk。好主意,没有考虑到。请创建一个答案,我会接受它作为正确的方法。

标签: c# .net search xamarin portable-class-library


【解决方案1】:

通常这是通过计时器完成的。当搜索文本更改时,您启动(或重用)一个计时器,该计时器将在延迟后触发并执行搜索请求。如果在该延迟期间输入了更多文本 - 计时器将重置。示例代码:

public class MyClass {
    private readonly Timer _timer;
    const int ThrottlePeriod = 500; // ms
    public MyClass() {
        _timer = new System.Threading.Timer(_ => {
            ExecuteRequest();
        }, null, Timeout.Infinite, Timeout.Infinite);
    }

    private string _searchTerm;
    public string SearchTerm
    {
        get { return _searchTerm; }
        set
        {
            _searchTerm = value;
            ResetTimer();
        }
    }

    private void ResetTimer() {
        _timer.Change(ThrottlePeriod, Timeout.Infinite);
    }

    private void ExecuteRequest() {
        Console.WriteLine(SearchTerm);
    }
}

如果计时器不可用,您可以对 Task.Delay 执行相同操作:

public class MyClass
{        
    const int ThrottlePeriod = 500; // ms
    private string _searchTerm;
    public string SearchTerm
    {
        get { return _searchTerm; }
        set
        {
            _searchTerm = value;
            SearchWithDelay();              
        }
    }

    private async void SearchWithDelay() {
        var before = this.SearchTerm;
        await Task.Delay(ThrottlePeriod);
        if (before == this.SearchTerm) {
            // did not change while we were waiting
            ExecuteRequest();
        }
    }        

    private void ExecuteRequest()
    {
        Console.WriteLine(SearchTerm);
    }
}

【讨论】:

    【解决方案2】:

    实现此功能的廉价/快速方法是Task.Delay

    var mySearchThread = new Thread (new ThreadStart (async delegate {
       while (true) {
           if (!String.IsNullOrWhiteSpace(seachText) {
              YourSearchMethod(seachText)
           };
    
           InvokeOnMainThread ( () => {
              // Refresh your datasource on the UIthread
           });
    
           await Task.Delay (2000);
       }
    })).Start ();
    

    基于 PCL 的解决方案(以及具有出色框架的惊人清洁方式)是使用ReactiveUI 节流(Throttle),然后您就可以大展宏图了喜欢:

    // Throttle searching to every 2 seconds
    this.WhenAnyValue(x => x.SearchText)
        .Where(x => !String.IsNullOrWhiteSpace(x))
        .Throttle(TimeSpan.FromSeconds(2))
        .InvokeCommand(SearchCommand)
    

    参考:http://reactiveui.net

    参考:http://docs.reactiveui.net/en/user-guide/when-any/index.html

    【讨论】:

    • 那不是每 2 秒执行一次服务请求吗?
    • @Evk 是的,根据您选择退出 while (true) 的时间(即,只要 SearchBar 存在),如果搜索文本已更改并更新 Ui,则每 2 秒过滤一次您的数据源。 ..便宜/快速..但我更喜欢在ReactiveUI中使用Command节流并将其与我的搜索视图模型联系起来
    • 即使没有命令和其他东西,也有更好的方法来使用 Task.Delay,而不是每 2 秒不断轮询无用请求的服务。如果我去吃午饭,然后把输入框放在一边怎么办?
    • @Evk 您的假设是您的数据是静态的,我的假设不是(动态数据和反应式用户界面,因为这通常是移动应用程序)。同样,fast/cheap... 您的答案必须通过绑定到动态数据源来调整,以便将您的逻辑置于清洗/冲洗/报告模式中,无论哪种方式,如果搜索文本发生更改并检查数据更改,更新数据过滤器,更新 UI...
    • 我不假设数据是静态的——我假设用户输入了他的搜索请求并且只想在用户停止输入时执行它(就像在第二个示例中为您做的反应式扩展一样)。另一方面(在第一个示例中),您将只是在随机点执行它,即使他根本没有输入任何内容。它与反应式用户界面、命令或您可能使用的任何现代术语无关 - 它只是不应该在实际应用程序中使用的糟糕代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-22
    • 2019-03-18
    • 2019-11-23
    • 1970-01-01
    • 2019-12-24
    • 2014-09-08
    相关资源
    最近更新 更多