【问题标题】:C# Queue Latest Asynchronous TaskC# 队列最新的异步任务
【发布时间】:2023-06-22 04:42:01
【问题描述】:

我正在开发一个 WPF 应用程序,只要数据绑定到滑块的属性发生更改,我就需要在其中执行冗长的操作。有没有一种简单的方法来为这个操作排队异步任务,但确保只运行最近排队的任务?

【问题讨论】:

  • 您是在问如何限制滑块触发的事件吗?这与排队不同。 .NET 具有允许您将数据排队等待处理 (ActionBlock<T>)、仅保留其中一个以进行处理 (BroadcastBlock<T> 或限制事件流以便每 X 或 T 秒只有一个通过 Observable.Throttle 的机制。这些是*不同的。我会说你想要节流
  • 限制听起来确实是一个不错的解决方案。你能提供一个我将如何实现它的例子吗?

标签: wpf task-parallel-library


【解决方案1】:

您可以使用单个任务,取消它,然后重新分配一个新任务。可以延迟链接任务以使滑块去抖动:

CancellationTokenSource cancel;
Task task;
...

cancel?.Cancel();
cancel?.Dispose();
cancel = new CancellationTokenSource();
task = Task.Delay(3000, cancel.Token).ContinueWith(...);

【讨论】:

  • 在这种情况下,冗长的任务是将数据推送到设备,因此取消它会产生令人讨厌的副作用。
  • @NeilThiessen 我已经编辑了我的答案,所以它只取消了延迟任务。
  • 您还可以延迟绑定的更新,方法是向其添加Delay。这对于可以快速更改其值的属性很有用。延迟计时器在第一次更新时启动,但只有在计时器完成后才会真正更新支持属性,但它将使用 latest 值进行更新。你可以这样使用&lt;TextBlock Text="{Binding Name, Delay=500}"/&gt;
  • @BradleyUffner 每次值更改时延迟计时器都会重置,因此在您的示例中,滑块需要在属性更新之前暂停 500 毫秒。我需要尽可能快地更新属性,并且我的长时间运行的任务也需要尽可能快地运行,尽管是异步的。请查看我刚刚发布的解决方案的答案。
【解决方案2】:

我设法通过使用Task.ContinueWith() 创建任务链来解决我的问题。线程安全计数器确保只有链中的最后一个任务真正运行。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace WpfApp1
{
    public class AsyncTaskRunner
    {
        #region Member Variables

        Task m_TaskChain;
        int m_TaskCount;

        #endregion

        #region Constructors

        public AsyncTaskRunner()
        {
            //Initialize the member variables
            m_TaskChain = Task.CompletedTask;
            m_TaskCount = 0;
        }

        #endregion

        #region Public Methods

        public void Run(Action action)
        {
            //Add a continuation to the task chain using the specified action
            Interlocked.Increment(ref m_TaskCount);
            m_TaskChain = m_TaskChain.ContinueWith((prevTask) =>
            {
                //Call the action if we're the last task in the task chain
                if (Interlocked.Decrement(ref m_TaskCount) == 0)
                {
                    action();
                }
            });
        }

        public async Task WaitAsync()
        {
            //Wait for the asynchronous task chain to finish
            await m_TaskChain;
        }

        #endregion
    }
}

【讨论】:

    【解决方案3】:

    您可以将Delay 添加到执行此操作的Binding

    MSDN 特别指出 Slider 是使用它的好人选:

    如果您使用数据绑定来更新数据源,您可以使用 延迟属性以指定属性后经过的时间量 在源更新之前对目标进行更改。例如,假设 你有一个 Slider 有它的 Value 属性数据双向绑定 到数据对象的属性和 UpdateSourceTrigger 属性是 设置为 PropertyChanged。在这个例子中,当用户移动 Slider,源会针对 Slider 移动的每个像素进行更新。这 源对象通常只需要滑块的值 滑块的值停止变化。为了防止更新源 通常,使用延迟指定不应更新源 直到拇指停止移动后经过一定时间。

    可以这样使用:

    <TextBlock Text="{Binding Name, Delay=500}"/>
    

    【讨论】:

    • 我需要长时间运行的任务在滑块变化时尽可能快地运行。 Delay 只是阻止绑定更新,直到滑块停止移动...