关于从后台线程更新的另一个问题。
要明白:在应用程序中,后台线程需要更新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
,因为它需要更少的东西(方法委托),而且更具可读性。
我错过了什么,我怎样才能安全地进行线程调用?我只想在列表框中添加一条消息。我真的不在乎调用线程是否会等待几毫秒。
答案 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个导致错误的公因:
错误是
的引入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实现):
Text.FontsCache
必须将当前线程ID添加到用于在缓存中存储创建的字体的ID。Text.FontsCache
的实例。