2

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

4

2 回答 2

2

在这种情况下,您应该做的是让工作线程对网站进行轮询,并将它找到的所有数据排队到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。示例中的重点是。

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

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

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

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

  • UI 和工作线程更加松散耦合。事实上,双方都对对方一无所知。
  • UI 线程可以自行决定应用更新的频率。无论如何,这确实是应该的。
  • 没有昂贵的编组操作。
  • UI 和工作线程的执行都不会受到对方的阻碍。您可以在两个线程上获得更多吞吐量。
于 2013-11-01T14:41:39.077 回答
1

你真正需要的是一个类来保存数据......

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);
    }
{
于 2013-11-01T04:51:03.770 回答