【问题标题】:Handling realtime data处理实时数据
【发布时间】:2013-11-01 04:12:44
【问题描述】:

我的程序将从网站请求实时数据。这些数据随时可能发生变化,所以我需要反复频繁地请求它来监控变化。数据将与一些移动平均线一起以直方图的形式显示在 Windows 窗体图表上。基于这些信息,用户需要能够与表单进行交互,以便为程序的一部分设置参数,以便根据传入的数据采取行动。我应该如何处理这些数据?我目前的计划是有一个单独的线程收集数据并将其写入主窗体,但我不确定如何在没有 A) 使界面无响应和 B) 启动另一个线程的情况下监视该数据的更改。由于显而易见的原因,A 是不可接受的,如果我要做 B,我觉得我还不如把代码扔到收集数据的线程中。

【问题讨论】:

    标签: c# multithreading real-time code-organization project-organization


    【解决方案1】:

    在这种情况下,您应该做的是让工作线程轮询网站并将它找到的所有数据排队到ConcurrentQueue。然后让您的 UI 线程定期轮询此队列以获取新数据。您确实希望该工作线程与 UI 线程进行交互。不要在这种情况下使用Control.Invoke 或其他封送技术。

    public class YourForm : Form
    {
      private CancellationTokenSource cts = new CancellationTokenSource();
      private ConcurrentQueue<YourData> queue = new ConcurrentQueue<YourData>();
    
      private void YourForm_Load(object sender, EventArgs args)
      {
        Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning);
      }
    
      private void UpdateTimer_Tick(object sender, EventArgs args)
      {
        YourData item;
        while (queue.TryDequeue(out item))
        {
          // Update the chart here.
        }
      }
    
      private void Worker()
      {
        CancellationToken cancellation = cts.Token;
        while (!cancellation.WaitHandle.WaitOne(YOUR_POLLING_INTERVAL))
        {
          YourData item = GetData();
          queue.Enqueue(item);
        }
      }   
    }
    

    上面的例子是基于 WinForms 的,但是同样的原则也将延续到 WPF 中。示例中的重点是。

    • 使用System.Windows.Timer.Timer(如果使用 WPF,则使用等效项)从使用TryDequeue 的队列中提取数据项。将滴答频率设置为在快速刷新屏幕之间提供良好平衡的值,但不要太快以至于它支配 UI 线程的处理时间。
    • 使用任务/线程从网站获取数据并使用Enqueue 将其加载到队列中。
    • 使用CancellationTokenSource 通过Cancel 取消操作(我没有包括在示例中),并通过在CancellationToken 提供的WaitHandle 上调用WaitOne 来驱动轮询间隔。

    虽然使用Control.Invoke 或其他编组技术不一定是坏事,但它也不是人们常说的灵丹妙药。以下是使用这种技术的一些缺点。

    • 它将 UI 和工作线程紧密耦合。
    • 工作线程决定了 UI 应该多久更新一次。
    • 这是一项昂贵的操作。
    • 工作线程必须等待 UI 线程处理消息,因此吞吐量会降低。

    让 UI 线程轮询更新的优点如下。

    • UI 和工作线程的耦合更加松散。事实上,双方都对对方一无所知。
    • UI 线程可以自行决定应用更新的频率。无论如何,这确实是应该的。
    • 没有昂贵的封送操作。
    • UI 和工作线程的执行都不会受到对方的阻碍。您可以在两个线程上获得更高的吞吐量。

    【讨论】:

    • 我认为我的整个问题都源于使用 .Invoke 的冲动,但您能否明确解释一下为什么这是一个坏主意?
    【解决方案2】:

    您真正需要的只是一个保存数据的类...

    class DataContainer
    {
        readonly byte[] _dataFromWeb;
    
        DataContainer(byte[] data)
        {
            _dataFromWeb = data;
        }
    
        public byte this[int index]
        {
            get
            {
                return _dataFromWeb[index];
            }
        }
    
        public int Length
        {
            get
            {
                return _dataFromWeb.GetUpperBound(0)+1;
            }
        }
    }
    

    ...和一个线程安全的对象指针,指向存放数据的对象。

    class SafePointerContainer
    {
        static public SafePointerContainer Instance =  new SafePointerContainer();
    
        public DataContainer _data = null;
        private SafePointerContainer() {}
    
        public DataContainer Data
        {
            get 
            { 
                lock(this)
                {
                    return _data;
                }
            }
            set
            {
                lock(this)
                {
                    _data = value;
                }
            }
        }
    

    当 UI 线程需要读取数据时,它应该获取指针(以线程安全的方式)并将其放入局部变量中。然后它可以随意使用指针访问所有成员变量。

    DataContainer latestData = SafePointerContainer.Instance.Data;
    for (int i=0; i<latestData.Length; i++)
    {
        DisplayData(i, latestData[i]);
    }
    

    当工作线程需要更新数据时,它应该实例化一个新的对象实例,然后更新指针(以线程安全的方式)指向新的实例。关键是在设置指针之前更新成员。为了强迫你这样做,我使用 readonly 关键字实现了数据,这意味着它只能在构造函数中设置。

    DataContainer newData = new DataContainer(dataJustObtainedFromTheWeb);
    SafePointerContainer.Instance.Data = newData;
    

    为上述类赋予有意义的名称,并更改 DataContainer 的内容以适应您从 Web 检索到的任何数据。对于奖励积分,使用泛型重新实现。哒哒。


    UI 线程如何知道数据发生了变化?

    UI 线程可以像检查任何其他类或变量一样检查 DataContainer。 ANY 线程如何知道任何变量是否已更改?检查它,并将其与最后一个值进行比较。

    DataContainer _oldData = null;
    while(!UserClickedExit())
    {
        DataContainer newData = SafePointerContainer.Instance.Data;
        if (newData != _oldData)
        {
            RenderData(newData);
            _oldData = newData;
        }
        else
        {
            System.Threading.Thread.Sleep(1000);
        }
    {
    

    【讨论】:

    • 不错的答案,但修正这句话:`它应该新建一个对象的新实例`
    • 每当数据发生变化时,UI 都需要读取数据。它应该实时反映数据。不过,只有读取数据的线程会知道这一点。
    • 我组织中的一位技术培训师一直使用“new up”这个表达方式,而不是实例化。我以为所有的年轻人都这样说话!已编辑。
    • 就我个人而言,我会让SafePointerContainer 实现INotifyPropertyChanged 让任何关心Data 中的数据已经改变而不是极化的人知道。
    • 我想你可能会遇到thread affinity problems,Scott。
    猜你喜欢
    • 2011-08-03
    • 2020-03-31
    • 2012-10-17
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 2023-01-08
    • 2021-12-17
    相关资源
    最近更新 更多