从后台线程更新 UI

我有一个后台任务,它位于从外部硬件设备读取数据的循环中。设备公开一个每 100 毫秒递增的“计数器”——当这个值改变时,我从设备中获取下一个数据值并引发一个事件。伪代码:-


public event HasDataEventArgs DataReceived;


while (some condition)

{

    // Wait for device counter to increment

    while (DeviceCounterValue == _lastDeviceCounterValue)

    {

       Thread.Sleep(3);

    }

    _lastDeviceCounterValue = DeviceCounterValue;


    // Read data value from device

    var data = GetDataFromDevice();


    // Raise my event

    DataReceived(this, new HasDataEventArgs(data)); 

}

我还有一个订阅此事件的 UI 视图。事件处理程序在图表上绘制数据值并设置许多绑定属性。


大多数情况下,这一切都很好,但是如果我(比如)拖动一个窗口或打开一个模态对话框,它偶尔会导致数据丢失。似乎发生的是外部设备计数器继续递增,但“while”循环实际上已短暂停止,因此错过了这些更改。很偶尔我会看到,即使我不是瞎搞与在UI什么相同的效果。


视图中的事件处理程序没有做太多事情,但这是一个复杂的桌面应用程序,其他后台线程更新其他绑定控件。也许这个特定的过程只是在性能方面使事情超出了边缘,尤其是当我开始拖动窗口时?


我想知道是否可以将事件处理程序代码包装在 a 中Task.Run(),(我假设)这会导致它立即将控制权返回给 while 循环,而不必等待事件处理程序执行它的操作。不过,这听起来很hacky - 我是否在为这样的事情找麻烦,特别是考虑到事件处理程序将被调用的频率(每 100 毫秒)?


慕运维8079593
浏览 115回答 2
2回答

慕妹3146593

您遗漏了一些细节,主要是事件处理程序在 GUI 线程上执行的操作。Invoke 或 BeginInvoke 等。但如果保护您的数据最重要,还有另一种选择:将新数据推送到 ConcurrentQueue。引发 Received 事件是可以的,但可选,您可能不需要它。主线程可以在自己的时间清空队列。例如使用定时器。您的屏幕更新仍然会断断续续,但您不应再丢失数据。

森林海

您必须拆分(线程方式)两件事:后台工作和绘图工作。一般来说,这是如何做这些事情的方式,但要具体 - 如果您的绘图需要时间,那么您的工作线程可能无法按时处理传入的数据,您可能会丢失一些数据/ 省略(这就是您实际观察到的)。这是一种方法(该方法必须是 UI 类的成员 - 窗口、用户控件等):void OnDataReceived(object sender, DataEventArgs e){    // here we're in the context of the working thread    // this call will return immediately giving control back to the working thread    Dispatcher.BeginInvoke(        DispatcherPriority.Normal,        (Action)delegate        {            // here we are in the context of the UI thread        });}
打开App,查看更多内容
随时随地看视频慕课网APP