WinForms多线程数据绑定场景,最佳实践?

9 浏览
0 Comments

WinForms多线程数据绑定场景,最佳实践?

我目前正在设计/改进一个应用程序的数据绑定部分,该应用程序广泛使用winforms数据绑定和来自后台线程的更新(每秒一次,超过100条记录)。

假设该应用程序是一个股票交易应用程序,后台线程监视数据变化并将其放入数据对象中。这些对象存储在BindingList<>中,并通过INotifyPropertyChanged实现传播到winforms控件的数据绑定来传播更改。

此外,数据对象目前通过WinformsSynchronizationContext.Send将更改传递到UI线程。

用户可以在UI中输入一些值,这意味着某些值可以从两个方向进行更改。用户的值不应被更新覆盖。

所以我脑海中有几个问题:

  • 是否有通用的设计准则来处理这个(数据绑定中的后台更新)?
  • 何时以及如何在UI线程上进行封送?
  • 后台线程与绑定/数据对象的最佳交互方式是什么?
  • 应该使用哪些类/接口?(BindingSource, ...)
  • ...

UI并不真正知道有一个后台线程会更新控件,根据我的理解,在数据绑定场景中,UI不应该知道数据来自何处...您可以将后台线程视为将数据推送到UI的东西,所以我不确定后台工作线程是否是我正在寻找的选项。

有时,您希望在数据/业务对象的操作过程中获得一些UI响应(例如,在重新计算期间设置背景)。引发绑定到后台的状态属性的propertychanged不足够,因为计算完成后,控件会被重新绘制。我的想法是挂接到propertychanged事件并在控件上调用.update()...

对此有其他想法吗?

0
0 Comments

WinForms多线程数据绑定场景最佳实践

在WinForms中,当涉及到多线程数据绑定的场景时,可能会遇到以下问题:如何保持数据更新的同时不阻塞UI线程,而不需要每一行代码都考虑锁定或跨线程调用。

解决方法之一是使用System.ComponentModel.BackgroundWorker。它可以很好地规范与派生后台线程的交互方式。可以通过调用ReportProgress方法在后台线程需要与UI通信时,使用ProgressChanged事件向UI线程传递进度。此外,BackgroundWorker还可以将任何对象附加到该事件,因此不需要进行手动的线程间通信。

如果后台线程频繁触发进度更新事件,可以考虑使用"Pull vs. Push models for UI updates"这篇文章中介绍的方法。该文章由Ayende撰写,详细讨论了UI更新的拉模型和推模型。通过使用拉模型,可以更好地控制UI线程的速度和后台线程的中断情况。

总之,System.ComponentModel.BackgroundWorker可能是解决多线程数据绑定问题的一部分,但关键问题是如何在保持数据更新快速的同时避免阻塞UI线程。使用BackgroundWorker可以避免手动的线程间通信,但仍需要根据具体情况选择合适的UI更新模型。

0
0 Comments

WinForms多线程数据绑定场景,最佳实践?

在WinForms中,多线程数据绑定是一种常见的需求。然而,编写多线程代码并进行正确的调用等操作可能会很麻烦,而且很难组织代码以便进行有效的测试。但是,UI只需要在每秒刷新几次,因此性能从来不是一个问题,而轮询方式将可以正常工作。

为了解决这个问题,我喜欢使用一个对象图,该对象图由一组后台线程不断更新。它们检查数据值的实际变化,并在注意到实际变化时更新对象图的根部(或每个主要项上,根据更合理的方式)的版本计数器,并更新值。

然后,前台进程可以使用一个定时器(默认与UI线程相同)每秒触发一次,并检查版本计数器,如果有变化,则锁定它(以防止部分更新),然后刷新显示。

这种简单的技术完全将UI线程与后台线程隔离开来,确保数据绑定的稳定性和性能。

代码示例(C#):

public class DataObject
{
    public int Version { get; set; }
    public string Value { get; set; }
}
public class BackgroundThread
{
    private readonly DataObject dataObject;
    public BackgroundThread(DataObject dataObject)
    {
        this.dataObject = dataObject;
    }
    public void UpdateValue(string newValue)
    {
        if (dataObject.Value != newValue)
        {
            dataObject.Version++;
            dataObject.Value = newValue;
        }
    }
}
public class MainForm : Form
{
    private readonly DataObject dataObject;
    private readonly BackgroundThread backgroundThread;
    public MainForm()
    {
        dataObject = new DataObject();
        backgroundThread = new BackgroundThread(dataObject);
        var refreshTimer = new Timer();
        refreshTimer.Interval = 1000; // 1 second
        refreshTimer.Tick += RefreshTimer_Tick;
        refreshTimer.Start();
    }
    private void RefreshTimer_Tick(object sender, EventArgs e)
    {
        lock (dataObject)
        {
            // Refresh UI only if the version has changed
            // This ensures atomic updates and prevents partial updates
            if (dataObject.Version > 0)
            {
                RefreshUI(dataObject.Value);
                dataObject.Version = 0;
            }
        }
    }
    private void RefreshUI(string value)
    {
        // Update UI with the new value
        // ...
    }
}

通过以上的解决方案,我们可以在WinForms中实现多线程数据绑定,并保证UI线程与后台线程的隔离,确保数据的稳定性和性能。

0
0 Comments

在WinForms中使用多线程数据绑定时,会遇到一些问题。大部分的“解决方案”都会导致大量的自定义代码和对BeginInvoke()或System.ComponentModel.BackgroundWorker的多次调用(BackgroundWorker本身只是对BeginInvoke的一个薄包装)。

过去,我发现你希望在数据稳定之前延迟发送INotifyPropertyChanged事件。处理一个属性更改事件的代码通常需要读取其他属性。你经常有一个控件,当其中一个属性的状态发生改变时需要重新绘制自己,而你又不希望控件过于频繁地重新绘制。

首先,每个自定义WinForms控件应该在PropertyChanged事件处理程序中读取其绘制所需的所有数据,这样它在接收到一个WM_PAINT(OnPaint)消息时就不需要锁定任何数据对象。控件不应立即在接收到新数据时重新绘制自己;而是应该调用Control.Invalidate()。Windows会将WM_PAINT消息合并成尽可能少的请求,并只在UI线程没有其他任务时发送它们。这样可以最大程度地减少重新绘制的次数和数据对象被锁定的时间。(标准控件大多已经使用数据绑定实现了这一点)

数据对象需要在进行更改时记录已发生的更改,一旦一组更改完成,就会“触发”UI线程调用SendChangeEvents方法,该方法然后在UI线程上调用PropertyChanged事件处理程序,处理所有已更改的属性。在SendChangeEvents()方法运行时,数据对象必须被锁定,以防止后台线程对它们进行更新。

当从数据库中读取一组更新时,可以通过调用BeginInvoke来“触发”UI线程。通常最好使用定时器让UI线程轮询,因为Windows只在UI消息队列为空时发送WM_TIMER消息,从而使UI感觉更加响应。

还要考虑是否根本不使用数据绑定,而是在定时器触发时UI询问每个数据对象“有什么变化”。数据绑定看起来很好,但很快就可能成为问题的一部分,而不是解决方案的一部分。

由于锁定/解锁数据对象很麻烦,而且可能无法快速从数据库中读取更新,可以将虚拟的数据对象副本传递给UI线程。使数据对象持久/不可变,以便对数据对象进行任何更改都会返回一个新的数据对象,而不是更改当前数据对象,可以实现这一点。

持久化对象听起来非常慢,但实际上并非如此,请参考这里和这里的一些指示。还可以参考Stack Overflow上的这个和这个问题。

还可以看一下retlang - .NET中的消息并发处理。它的消息批处理可能会有用。

(对于WPF,我会在UI线程中设置一个View-Model,然后在后台线程中从多线程模型中“批量”更新它。然而,WPF在组合数据绑定事件方面要比WinForms好得多。)

0