从后台线程更新UI

时间:2012-04-12 09:30:05

标签: c# winforms multithreading .net-3.5 invocation

关于从后台线程更新的另一个问题。

要明白:在应用程序中,后台线程需要更新UI。我考虑使用中间集合来缓冲消息,并有一个计时器来显示它们。目前我们正在尝试最简单的方法。

代码尝试#1:

void foo(string status)
{
    if (this.InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(delegate()
        {
            InsertStatusMessage(status);
        }));

    }
    else
    {
        InsertStatusMessage(status);
    }  
}

这似乎有一些缺陷。如果尚未创建窗口句柄(在我看来不可用),Msdn表示InvokeRequired也返回false。所以代码应该是:

void foo(string status)
{
    if (this.InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(delegate()
        {
            InsertStatusMessage(status);
        }));

        // wait until status is set
        EndInvoke(result);
    }
    else if(this.IsHandleCreated)
    {
        InsertStatusMessage(status);
    }
    else
    {
        _logger.Error("Could not update status");
    } 
}

上面的代码也会以某种方式抛出(对于未知且未复制的原因)。我们使用DevExpress,这是未处理的异常消息(没有信息也没有任何关于错误发生在何处/何处的线索):

  

System.NullReferenceException:未将对象引用设置为实例   一个物体的   DevExpress.Utils.Text.FontsCache.GetFontCacheByFont(图形图形,   字体字体)in   DevExpress.Utils.Text.TextUtils.GetFontAscentHeight(Graphics g,Font   字体)   DevExpress.XtraEditors.ViewInfo.BaseEditViewInfo.GetTextAscentHeight()   在   DevExpress.XtraEditors.ViewInfo.TextEditViewInfo.CalcTextBaseline(图形   杜松子酒   DevExpress.XtraEditors.ViewInfo.BaseControlViewInfo.ReCalcViewInfo(图形   g,MouseButtons按钮,Point mousePosition,Rectangle bounds)   DevExpress.XtraGrid.Views.Grid.ViewInfo.GridViewInfo.UpdateCellEditViewInfo(GridCellInfo   cell,Point mousePos,Boolean canFastRecalculate,Boolean calc)in   DevExpress.XtraGrid.Views.Grid.ViewInfo.GridViewInfo.CreateCellEditViewInfo(GridCellInfo   cell,Boolean calc,Boolean allowCache)in   DevExpress.XtraGrid.Views.Grid.ViewInfo.GridViewInfo.RequestCellEditViewInfo(GridCellInfo   细胞)   DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRegularRowCell(GridViewDrawArgs   e,GridCellInfo ci)in   DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRegularRow(GridViewDrawArgs   e,GridDataRowInfo ri)in   DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRow(GridViewDrawArgs   e,GridRowInfo ri)in   DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRows(GridViewDrawArgs   e)in   DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawContents(GridViewDrawArgs   e)in   DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.Draw(ViewDrawArgs   ee)在DevExpress.XtraGrid.Views.Base.BaseView.Draw(GraphicsCache   e)在DevExpress.XtraGrid.GridControl.OnPaint(PaintEventArgs e)中   在System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs   e,Int16 layer,Boolean disposeEventArgs)in   System.Windows.Forms.Control.WmPaint(Message& m)in   System.Windows.Forms.Control.WndProc(Message& m)in   DevExpress.XtraEditors.Container.EditorContainer.WndProc(消息& m)
  在DevExpress.XtraGrid.GridControl.WndProc(Message& m)中   System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
  在System.Windows.Forms.NativeWindow.Callback中(IntPtr hWnd,Int32 msg,   IntPtr wparam,IntPtr lparam)

我想使用Begin/End Invoke而不是Invoke,因为它需要更少的东西(方法委托),而且更具可读性。

我错过了什么,我怎样才能安全地进行线程调用?我只想在列表框中添加一条消息。我真的不在乎调用线程是否会等待几毫秒。

3 个答案:

答案 0 :(得分:2)

您可以使用“MethodInvoker”直接调用“Invoke”。

void foo(string status)
{
    Invoke(new MethodInvoker(() => {InsertStatusMessage(status);}));
}

我也使用了DevExpress控件(特别是在一个表单上异步更新几个Xtragrids上的数据源)。

有关MethodInvoker的更多信息,有excellent post

答案 1 :(得分:1)

class Test : Form
{
        delegate void FooCallback(string status);


        public Test()
        {
        }

        private void foo(string status)
        {
            if (this.InvokeRequired == true)
            {
                FooCallback = new FooCallback(foo);
                this.Invoke
                    (d, status);

            }
            else
            {
              //Do Things

            }
        }     
}

使用MethodInvoker成本来提高性能

答案 2 :(得分:0)

我对所应用的逻辑进行了逆向工程,并且在所有情况下都有1个导致错误的公因:

  1. 未将对象引用设置为对象的实例。
  2. 目前正在其他地方使用对象。
  3. 错误是

    的引入
    DevExpress.Utils.Text.FontsCache.GetFontCacheByFont(Graphics graphics, Font font)
    

    此类在线程之间共享Windows字体,这是STRICTLY FORBIDDEN。在绘制消息处理期间Windows的GDI资源使用情况以及

    等内容
    DevExpress.Utils.Text.TextUtils.GetStringSize(Graphics g, String text, 
        Font font, StringFormat stringFormat, Int32 maxWidth, Int32 maxHeight, 
        IWordBreakProvider wordBreakProvider, Boolean& isCropped)
    

    仅限于已创建GDI资源的线程。换句话说:

      

    只有创建了字体或窗口的线程才可以使用它,所有其他线程都不允许将窗口绘制消息发送到这些窗口。

    测量窗口中字体的大小也是某种类型的绘制操作(虽然不可见,因为只返回绘制文本的大小)。

    解决方案(两者中只有一个应该由DevExpress实现):

    1. Text.FontsCache必须将当前线程ID添加到用于在缓存中存储创建的字体的ID。
    2. 每个主题必须创建自己的类Text.FontsCache的实例。