如何在不冻结UI的情况下连续更新数据网格(或任何其他UI控件)?

bob*_*lex 7 c# user-interface winforms

在WinForms应用程序中,我有一个与数据源关联的数据网格.当数据通过后台线程进入时,需要更新数据集,然后自动更新数据网格.现在,更新可以是每20秒更新7000次.问题是当这样的更新发生时UI会挂起,因为它必须发生在主线程上.这个问题有没有明确的解决方案?

通常,如何在WinForms中设计高性能的企业应用程序,其中UI在不冻结应用程序的情况下持续更新?


添加方案来解释这个:

考虑这种情况.您有一个树视图,用于表示某些分层数据.现在树上的数据更新是异步的.服务器可以同时发布一个或1000个更新.更新可以是对现有项的修改或添加新节点.需要注意的是,更新不能延迟.节点代表某个地方的实时实体.延迟更新将使用户感觉事件本身被延迟.所以这件事无法完成.如果有可能(从业务逻辑的角度来看)我会在很长一段时间内完成它.

这里有一个关键点:所有数据不需要同时可见.

所以人们不再建议这样了:

添加后台工作线程将无法帮助,因为线程必须切换到主线程才能执行更新.工作线程没有任何区别.

Kev*_*ick 6

你不能,除非你想使用DirectX.

Windows窗体不适用于实时信息显示.正如许多其他人所指出的那样,你可以非常接近,但是由于Windows消息循环的工作方式,你绝对不能保证屏幕上的内容是"实时"的,即使你创建一个滴答的定时器在60hz.即使您以事件驱动的方式执行此操作,Windows仍会排队WM_PAINT消息,如果您正在寻找实时显示,则该消息将不可避免地延迟.

如果您真的想要一个非常接近实时的显示器,您将需要实现类似于游戏循环的东西.

有关Windows消息循环无法用于实时显示以及游戏循环的原因的说明,请参阅:http: //www.mvps.org/directx/articles/writing_the_game_loop.htm

计算机游戏不能有任何可感知的延迟,因此大多数计算机游戏都试图优化性能,以便它们接近60hz的圣杯或更高的帧速率.(电影仅以24hz投影,你认为它们是"延迟"吗?)

使用实时显示编写应用程序并非易事,我强烈建议考虑通过以下任何方式对Windows提供的内容进行攻击:

  • 创建一个计时器,以可接受的速率(每秒10次或更多次)排队屏幕更新.用户不会将事件视为延迟,因为用户无法察觉在一小部分时间内发生的延迟.
  • 在基础数据发生变化时引发事件,让Windows决定何时更新显示(这几乎总是可以接受).
  • 如果可能,请提供一个非基于网格的备用显示器.也许是滚动控制台或其他一些显示相关信息而不会覆盖旧信息的界面.这可能不适用,但是当您想要的界面不起作用时,提出另一种界面想法通常是一种很好的方法.

如果你真的,真的仍然需要一个非常高性能的用户界面,并编写一个游戏循环,你可以在C#中这样做,并自己绘制一个网格到DirectX表面.一旦掌握了DirectX,绘制网格相当容易,它只是一堆线.使用此方法,您将避免处理Windows消息循环,并可能接近实时性能.

这是一个很棒的教程,介绍如何使用DirectX以及如何在Windows窗体上呈现:

http://www.godpatterns.com/2005/02/using-directx-and-c-sharp-to-create.html


Jac*_*goń 5

在您的评论中,您说您的繁重处理经常报告进度,您不能丢弃任何报告(因为报告是需要显示的真实数据)。

您应该做的是实现(双)缓冲,向缓冲区报告进度,并且每隔一段时间只将缓冲区与 GUI 同步。

伪代码如下:

DataGrid Grid; // This displays the data
List<object> DataBuffer; // Frequent updates are performed on this list

void BackgroundThreadLoop()
{
   while(true) // This loop iterates 7000 times in 20 seconds
   {
       var result = DoSomeHeavyCalculations();

       // Depending on the nature of the result, you can either just add it to list
       // or perhaps modify existing entries in the list in some way.
       DataBuffer.Add(result); // The simple case
       PerformSomeUpdating(DataBuffer, result); // The complicated case
   }
}

Timer RefreshTimer;
override void OnLoad()
{
    RefreshTimer = new Timer();
    RefreshTimer.Interval = 500; // easy to experiment with this
    RefreshTimer.Tick += (s, ea) => DrawBuffer(DataBuffer);
}

void DrawBuffer(List<object> DataBuffer)
{
    // This should copy DataBuffer and put it in the grid as fast as possible.

    // How to do this really depends on how the list changes and what it contains.
    // If it's just a list of strings:
    Grid.DataSource = DataBuffer.ToList(); // Shallow copy is OK with strings

    // If it's a list of some objects that have meaningful Clone method:
    Grid.DataSource = DataBuffer.Select(o => o.Clone).ToList();

    // If the number of elements is like constant and only some values change,
    // you could use some Dictionary instead of List and just copy values.
}
Run Code Online (Sandbox Code Playgroud)

如果您提供更准确的信息,我可能会进一步提供帮助。

更新

有了新的细节,我建议缓冲对对象所做的个别更改。表示对某些对象结构的更改的最通用方法是函数(可能是无参数的Action)。在接收更改时,您可以构建直接修改视图绑定数据的更新函数并将它们存储在缓冲区中:

List<Action> UpdateBuffer;
void OnUpdateReceived(MyType objToModify, object newValue)
{
    // The point is to make the lambda (below) as efficient as you can; 
    // finding the object and preparing the update should be done here, so that
    // no time is wasted during redraw in the main thread.

    UpdateBuffer.Add(() => objToModify.ApplyNewValueInSomeWay(newValue));


    // some other method should be constructed to add data to the view, but you get the point
}
Run Code Online (Sandbox Code Playgroud)

现在DrawBuffer(名称不再完全足够,但无论如何)方法将很容易:

void DrawBuffer()
{
    List<Action> bufferCopy;
    lock(UpdateBuffer) // the other thread should also lock the buffer for adding
    {
        bufferCopy = UpdateBuffer.ToList();
        UpdateBuffer.Clear();
    }
    view.SuspendLayout();
    foreach(Action a in bufferCopy)
        a();
    view.ResumeLayout();
}
Run Code Online (Sandbox Code Playgroud)

显然我没有尝试过这个确切的解决方案,但它使您能够控制重绘频率和重绘整批而不是单个更新。


Art*_*ich 1

瓶颈是处理来自服务器的数据还是实际将其放入 DataGridView?如果是后者,VirtualMode 可以帮助您:http ://msdn.microsoft.com/en-us/library/2b177d6d.aspx 。