首页 > 解决方案 > 如何在 SelectionChanged 上运行昂贵的进程,但在快速滚动时却不行。(延迟事件处理程序)?

问题描述

每次触发 DataGrid 的 SelectionChanged 事件时,我都必须运行一个相当慢的任务。

我遇到的问题是我需要保持应用程序的响应性,如果用户使用箭头键快速滚动,那么我不想为每个项目执行任务。只有他们停下来的项目。(我希望这是有道理的!)

我已经设置了一个非常基本的示例来演示,它在 DataGrid 中显示单词列表,然后当您滚动浏览它们时,它会将它们添加到 ListView。

这是我到目前为止所尝试的:

CancellationTokenSource cts;
private bool loading;
private async void dgData_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (loading)
    {
        cts.Cancel();
        return;
    }

    cts = new CancellationTokenSource();

    loading = true;
    var x = dgData.SelectedItem.ToString();

    await Task.Run(async () =>
    {
        Thread.Sleep(1000); //Wait a second to see if scrolling quickly...
        await ExpensiveProcess(x);
    });

    loading = false;
}

private async Task ExpensiveProcess(string text)
{
    if (cts.IsCancellationRequested)
    {
        loading = false;
        return;
    }

    await Task.Factory.StartNew(() =>
    {
        //Expensive process will be done here...
    });

    Application.Current.Dispatcher.Invoke(() =>
    {
        lvwItems.Items.Add(text);
    });
    loading = false;
}

这似乎起作用的事实是,如果箭头快速向下它会错过项目,但是当我停在一个并希望它运行时它不起作用?

我哪里错了?这甚至是最好的方法吗?非常感谢任何建议,并乐于提供更多信息。先感谢您。

更新:

我在 YouTube 上找到了一个视频,它建议这样做,这正如我所期望的那样工作,所以现在我要这样做,但现在将问题留待任何反馈。

创建一个计时器,它将运行昂贵的过程并将间隔设置为低但不会太慢以便按键。

var myTimer = new DispatcherTimer();
myTimer.Interval = TimeSpan.FromSeconds(1);
myTimer.Tick += MyTimer_Tick

在计时器的滴答事件上运行长时间运行的进程。

private void MyTimer_Tick(object sender, EventArgs e)
{
    var x = dgData.SelectedItem.ToString();
    Task.Run(async () => 
    {
        Thread.Sleep(1000); //Needs to be removed
        await ExpensiveProcess(x);
    });
}

然后在常规的 SelectionChanged 事件中简单地停止和启动计时器。也不要忘记在漫长的过程结束时停止计时器。

标签: c#wpf

解决方案


您可以在事件处理程序中启动一个计时器,SelectionChanged然后在计时器结束时检查该项目是否仍处于选中状态。

如果是,则使用 a 调用长时间运行的方法,CancellationToken如果发生另一个选择,则取消该方法。

下面的示例代码应该给你的想法:

private CancellationTokenSource _cts = null;
...
dataGrid.SelectionChanged += async(ss, ee) =>
{
    //cancel any previous long running operation
    if (_cts != null)
    {
        _cts.Cancel();
        _cts.Dispose();
    }
    _cts = new CancellationTokenSource();
    //store a local copy the unique id or something of the currently selected item
    var id = (dataGrid.SelectedItem as TestItem).Id;
    //wait a second and a half before doing anything...
    await Task.Delay(1500);
    //if no other item has been selected since {id} was selected, call the long running operation
    if (_cts != null && id == (dataGrid.SelectedItem as TestItem).Id)
    {
        try
        {
            await LongRunningOperation(id, _cts.Token);
        }
        finally
        {
            _cts.Cancel();
            _cts.Dispose();
            _cts = null;
        }
    }
};

推荐阅读