1

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

切入正题:在应用程序中,后台线程需要更新 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.TextUtils.GetFontAscentHeight(Graphics g, Font font) 中的 DevExpress.Utils.Text.FontsCache.GetFontCacheByFont(Graphics graphics, Font font) 中的对象实例.XtraEditors.ViewInfo.BaseEditViewInfo.GetTextAscentHeight() 在 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo.CalcTextBaseline(Graphics g) 在 DevExpress.XtraEditors.ViewInfo.BaseControlViewInfo.ReCalcViewInfo(Graphics g, MouseButtons buttons, Point mousePosition, Rectangle bounds) 在 DevExpress. DevExpress.XtraGrid.Views.Grid.ViewInfo.GridViewInfo.CreateCellEditViewInfo(GridCellInfo cell, Boolean calc,Boolean allowCache) 在 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridViewInfo.RequestCellEditViewInfo(GridCellInfo cell) 在 DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRegularRowCell(GridViewDrawArgs e, GridCellInfo ci) 在 DevExpress.XtraGrid.Views.Grid .Drawing.GridPainter.DrawRegularRow(GridViewDrawArgs e, GridDataRowInfo ri) 在 DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRow(GridViewDrawArgs e, GridRowInfo ri) 在 DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawRows(GridViewDrawArgs e) 在 DevExpress.XtraGrid.Views.Grid.Drawing.GridPainter.DrawContents(GridViewDrawArgs e) 在 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) 在 System.Windows.Forms.Control.WmPaint(Message& m) 在 System.Windows.Forms.Control.WndProc(Message& m) 在 DevExpress .XtraEditors.Container.EditorContainer.WndProc(Message& 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因为它需要更少的东西(方法委托)并且更具可读性。

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

4

3 回答 3

2

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

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

我也将它与 DevExpress 控件一起使用(尤其是在一个表单上异步更新多个 Xtragrid 上的数据源)。

有关 MethodInvoker 的更多信息,有一篇很棒的帖子

于 2012-04-12T09:53:25.970 回答
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 会花费很多性能

于 2013-09-02T09:32:14.313 回答
0

我已经对应用逻辑进行了逆向工程,并且在所有情况下,有一个共同因素是导致错误的原因:

  1. 你调用的对象是空的。
  2. 对象当前正在其他地方使用。

错误是引入

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

此类在线程之间共享 windows 字体,这是严格禁止的。GDI 资源的 Windows 使用,在绘制消息处理期间以及类似的东西

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必须将 current-thread-ID 添加到用于将创建的字体存储在缓存中的 ID。
  2. 每个线程都必须创建自己的类实例Text.FontsCache
于 2015-11-12T08:46:50.103 回答