【问题标题】:Cancelling an Async Task When TextBox Text Changed Then Restart It当文本框文本更改时取消异步任务然后重新启动它
【发布时间】:2022-01-03 12:08:47
【问题描述】:

我有 .NET 应用程序,我的 GUI 上有 DataGridView 和 TextBox。我想要做的是当用户更改 TextBox 文本时,更新包含此文本的单元格的 DataGridView。但是这个搜索应该作为异步任务运行,因为如果不是,它会导致 GUI 冻结。每次当用户更改 TextBox 文本时,如果另一个搜索任务正在运行,我的应用程序应该取消并再次重新运行它以根据新的搜索值进行搜索。这是我的代码;

CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)    
{
    cts.Cancel();
    CancellationToken ct = cts.Token;
    try
    {
        await Task.Run(() =>
        {
            System.Diagnostics.Debug.WriteLine("Task started");

            // Searching here.

            System.Diagnostics.Debug.WriteLine("Task finished");
        }, cts.Token);
    }
    catch
    {
        System.Diagnostics.Debug.WriteLine("Cancelled");
    }
}

在我的代码中,任务在没有启动的情况下被取消。我只在调试控制台上看到“已取消”行。我应该取消任务,因为如果我不这样做,他们的数量和应用程序的 CPU 使用率就会增加。有没有办法做到这一点?

【问题讨论】:

  • 为什么你认为Token在你调用cts.Cancel();之后是有效的
  • 那么,你为什么相信这个代码:dotnetfiddle.net/rjGOZn - 会打印错误。
  • 我应该声明新的 CancellationTokenSource 吗?
  • 是的,你应该这样做。
  • 你继续在那里使用那个取消令牌...A Tour of Task, Part 9: Delegate Tasks

标签: c# .net multithreading async-await


【解决方案1】:

就像 Rand Random 所说,我应该清除 new CancellationTokenSource 对象。我已经像这样编辑了我的代码并且它有效。代码应该是这样的:

CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)    
{
    cts.Cancel();
    cts.Dispose();
    cts = new CancellationTokenSource();
    try
    {
        await Task.Run(() =>
        {
            System.Diagnostics.Debug.WriteLine("Task started");

            // Searching here.

            System.Diagnostics.Debug.WriteLine("Task finished");
        }, cts.Token);
    }
    catch
    {
        System.Diagnostics.Debug.WriteLine("Cancelled");
    }
}

【讨论】:

  • 我不确定您是否应该处理旧源。
  • 我认为它会减少内存使用量。
  • Cancel 不会立即发生,因此 Cancel 不会等待所有任务被实际取消,并且当您已经处置 CancellationTokenSource 时任务仍然可以运行,所以我还想说您应该'不要手动处理它,但这只是一种直觉,没有我可以提供的证据。让垃圾收集器处理它。
  • 可能是cts?.Cancel(); using(cts = new CancellationTokenSource()) { try {} catch {} cts = null; } 和声明CancellationTokenSource cts = null;
  • @RandRandom GC 不调用 Dispose ...
【解决方案2】:

如果您想让自己的生活更轻松,您应该考虑使用 Microsoft 的 Reactive Framework(又名 Rx)

我假设你可以写这个方法:

async Task<string[]> DoSearchAsync(string text, CancellationToken ct)
{
    /* you implement this method */
}

那么你可以这样做:

    private IDisposable _searchSubscription = null;

    private void Form1_Load(object sender, EventArgs e)
    {
        _searchSubscription =
            Observable
                .FromEventPattern(h => TextBox1.TextChanged += h, h => TextBox1.TextChanged -= h)
                .Throttle(TimeSpan.FromMilliseconds(400.0))
                .Select(ep => TextBox1.Text)
                .Select(text => Observable.FromAsync(ct => DoSearchAsync(text, ct)))
                .Switch()
                .ObserveOn(TextBox1)
                .Subscribe(results => { /* update your UI */ });
    }

现在这会监视您的 TextChanged 事件,等待 400.0 毫秒以防您输入另一个字符 - 如果用户仍在输入,则触发大量搜索是没有意义的 - 然后它会调用您的新 DoSearchAsync 方法。

然后它会执行Switch,这实际上意味着取消任何正在进行的搜索并开始新的搜索。

最后,它会编组回 UI 线程,然后为您提供搜索结果,以便您可以更新 UI。

它只为您处理所有调用、后台线程调用、编组回 UI。

如果你想关闭它,只需拨打_searchSubscription.Dispose()

NuGet System.Reactive.Windows.Forms 并添加 using System.Reactive.Linq; 以获取位。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-12
    • 2017-02-03
    相关资源
    最近更新 更多