- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我正在创建一个新的 Windows C# 窗体应用程序,它使用各种硬件系统(Kinect V2 和 3D RFID 定位系统)。这是一个科学应用程序,而不是游戏,但具有绝对类似于游戏的逻辑(需要收集和管理 3d env 对象的物理数据,渲染图形 3D 环境等)。在大多数原生 Windows 环境游戏中,您可能有一个基本的主循环,它在监听来自 mse 和 kbd 的 DirectInput 时调用逻辑更新和渲染函数。但由于这是 C#,它更像是一个事件驱动的环境,我相信我需要依赖 3 个选项之一:
(1) 创建一个新线程并将其用作逻辑更新和 3d 渲染的主循环(当然要监听转义逻辑),或者
(2) 创建一个应用程序空闲处理函数,检查应用程序是否空闲,然后处理逻辑,或者
(3) 覆盖 winproc 并在那里执行我的类似游戏的逻辑。
所以我创建了一个测试应用程序:它有一个时钟显示,并使用刚才提到的所有三种方法为每种方法独立计算 FPS 计数器。这衡量每种方法每秒迭代的频率,并每秒显示一次在时钟旁边。
但是结果不是我所期望的。我认为新线程将是最快的方法,类似于主循环。但事实并非如此。 WinProc 被调用最多(即使将线程的优先级设置为最高)。此外,应用程序空闲处理程序是最慢的(正如预期的那样,它只在空闲时调用!) - 但我已经读到空闲处理是处理窗口 C# 窗体游戏类型应用程序的规范方法。这三个都使用完全相同的逻辑来计算它们的 FPS(只是一个 int,每次调用它们的方法时都会得到一个++)。
以下是应用运行几秒钟并稳定后的结果:
基于线程的 FPS:3,200
应用程序空闲处理程序 FPS:600
WinProc 覆盖 FPS:10,000
我知道为什么空闲处理程序是最慢的。但为什么线程的迭代频率低于 winproc?我在那里错过了什么?此外,在与窗体窗口交互时,winproc 发现每秒迭代次数大幅增加(正如预期的那样),但线程也是如此!为什么仅仅因为与 GUI 交互,独立线程就会被更频繁地调用?我显然不理解这里的一些基本知识 - 有人可以给我一些线索吗?
更新 1: 这是 self 的问题的第一个版本被搁置以来的一次编辑 - 显然人们希望看到一些实际的代码!这不是“我不知道为什么我的代码不起作用”——我试图梳理性能……所有这些都有效。所以这是基本代码 - 希望这会有所帮助!
谢谢!
代码:ClockText 变量只是 3 个文本框,在每个方法中用时间戳更新,而 UPS 变量是用每个方法计算的 FPS 更新的文本框:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices; //for DllImport of c++ functionality for win msg processing
namespace Clock1
{
public partial class ClockForm : Form
{
//General app variables
System.Threading.Thread t;
bool KeepLooping = true;
DateTime StartTime = DateTime.Now;
DateTime StopTime = DateTime.Now;
TimeSpan TimePassed = new TimeSpan();
int FrameTick1 = 0;
int FrameTick2 = 0;
int FrameTick3 = 0;
//Imported for use of c++ win msg variables
//For message handler version
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
//Imported for winproc loop version processing on WM_PAINT (0x000F) messages (lowest allocation overhead)
[DllImport("user32.dll")]
public static extern int SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
//Primary form loop
public ClockForm()
{
//Loaded on program start
//Init components for C# environment
InitializeComponent();
//For winproc method, override painting (double check the functionality of this):
//SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
//Multithreaded method initialization:
t = new System.Threading.Thread(MainLoop);
t.Priority = System.Threading.ThreadPriority.Highest;
t.Start();
//Application idle event handling allocation:
Application.Idle += HandleApplicationIdle;
}
//Multithreading method primary func
public void MainLoop()
{
while (KeepLooping == true)
{
//Count Frames
FrameTick1++;
//Use Invoke because the new thread can't access UI elements directly
MethodInvoker MI = delegate()
{
//Update time:
ClockText.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");
};
ClockText.Invoke(MI);
}
//When KeepLooping is no longer true, exit:
Application.Exit();
Environment.Exit(1);
}
//Application idle event handling method primary func
void HandleApplicationIdle(object sender, EventArgs e)
{
while (IsApplicationIdle())
{
//Count Frames
FrameTick2++;
//Call frame update:
UpdatesPerSecond();
//Update time text
ClockText2.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");
}
}
//Application idle event handling method secondary func
bool IsApplicationIdle()
{
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}
//Winproc method (override for the WndProc handler):
protected override void WndProc(ref Message m)
{
//Updates with full FPS capacity (may be a CPU hit though, so consider doing this only on a certain event - maybe not WM_PAINT, but something else, a faster more regular heartbeat
//NOte: All messages are sent to the WndProc method after getting filtered through the PreProcessMessage method. Recursive infinite loop risk when working with windows control variables, due to updates and such
//Count Frames
FrameTick3++;
//Update clock text:
ClockText3.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");
//Proceed with regular message handling
base.WndProc(ref m);
}
private void UpdatesPerSecond()
{
//Time Calc:
StopTime = DateTime.Now;
TimePassed = StopTime - StartTime;
if (TimePassed.Seconds >= 1)
{
//Update UPS/FPS count displays:
UPS1.Text = FrameTick1.ToString("D");
UPS2.Text = FrameTick2.ToString("D");
UPS3.Text = FrameTick3.ToString("D");
//Reset UPS count:
FrameTick1 = 0;
FrameTick2 = 0;
FrameTick3 = 0;
StartTime = DateTime.Now;
TimePassed = TimeSpan.Zero;
}
}
//Event handeler for exit button
private void btnExit_Click(object sender, EventArgs e)
{
//Detach event handler for idle event processing
Application.Idle -= HandleApplicationIdle;
//Stop the multi-threaded method
KeepLooping = false;
}
}
}
更新 2: 我想我会在采纳下面 Zer0 的建议(这是正确的!)后添加更新,以防有一天它可能对其他人有所帮助。正如他所提到的,嵌入在每个处理方法中的用于 GUI 更新的 Invoke 方法导致了瓶颈和奇怪的结果。结果好多了!!!
我将所有 GUI 更新重新定位到计时器功能,应用程序的结果如下所示:
最后的代码,以防其他人想自己测试:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices; //for DllImport of c++ functionality for win msg processing
using System.Globalization;
namespace Clock1
{
public partial class VosClockForm : Form
{
//Timer for GUI updates:
System.Windows.Forms.Timer MainTimer = new System.Windows.Forms.Timer();
//General app variables
System.Threading.Thread t;
bool KeepLooping = true;
bool ExitApplicationNow = false;
DateTime StartTime = DateTime.Now;
DateTime StopTime = DateTime.Now;
TimeSpan TimePassed = new TimeSpan();
CultureInfo StrictCulture = CultureInfo.InvariantCulture;
long MethodTick1 = 0;
long MethodTick2 = 0;
long MethodTick3 = 0;
long FrameTick = 0;
//Imported for use of c++ win msg variables
//For message handler version
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
//Imported for winproc loop version processing
[DllImport("user32.dll")]
public static extern int SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
//Primary form loop
public ClockForm()
{
//Loaded on program start
//Init components for C# environment
InitializeComponent();
//If overriding winproc based painting, need to tell system we are doing the drawing
//SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
//Multithreaded method initialization:
t = new System.Threading.Thread(MainLoop);
t.Priority = System.Threading.ThreadPriority.Normal;
t.Start();
//Application idle event handling allocation:
Application.Idle += HandleApplicationIdle;
//Start the MainTimer for updates:
MainTimer.Tick += new EventHandler(MainTimerEvent);
MainTimer.Interval = 15; //15 ms = 67 fps max, may need to tweak this a bit!
MainTimer.Start();
}
//Multithreading method primary func (don't post GUI updates using Invoke here as it causes a huge bottleneck, holding up the mainloop to wait for a graphics update
public void MainLoop()
{
while (KeepLooping == true)
{
//Count Frames
MethodTick1++;
}
//When KeepLooping is no longer true, exit:
ExitApplicationNow = true;
}
//Application idle event handling method primary func
void HandleApplicationIdle(object sender, EventArgs e)
{
while (IsApplicationIdle())
{
//Count Frames
MethodTick2++;
}
}
//Application idle event handling method secondary func
bool IsApplicationIdle()
{
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}
//Winproc method (override for the WndProc handler): All messages are sent to the WndProc method after getting filtered through the PreProcessMessage method. Recursive infinite loop risk when working with windows control variables, due to updates and such
//Do NOT update any GUI elements in this function!
protected override void WndProc(ref Message m)
{
//Count Frames
MethodTick3++;
//Check for application exit (consider moving this to whatever function ends up handling periodic GUI updates and such)
if (ExitApplicationNow == true)
{
//Detach event handler for idle event processing
Application.Idle -= HandleApplicationIdle;
//Exit:
Application.Exit();
Environment.Exit(1);
}
//Proceed with regular message handling
base.WndProc(ref m);
}
private void UpdateInterface()
{
//Update Clock:
lblClock.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");
//GUI Update tick:
FrameTick++;
//UPS Calc:
StopTime = DateTime.Now;
TimePassed = StopTime - StartTime;
if (TimePassed.Seconds >= 1)
{
//Update GUI FPS
lblGuiFps.Text = FrameTick.ToString("###,###,###,###", StrictCulture);
//Update UPS/FPS count displays:
lblUPS1.Text = MethodTick1.ToString("###,###,###,###", StrictCulture);
lblUPS2.Text = MethodTick2.ToString("###,###,###,###", StrictCulture);
lblUPS3.Text = MethodTick3.ToString("###,###,###,###", StrictCulture);
//Reset UPS count:
MethodTick1 = 0;
MethodTick2 = 0;
MethodTick3 = 0;
FrameTick = 0;
StartTime = DateTime.Now;
TimePassed = TimeSpan.Zero;
}
}
//Event handeler for exit button
private void btnExit_Click(object sender, EventArgs e)
{
//Stop the multi-threaded method
KeepLooping = false;
}
//Timer Event Handling: GUI Updates and related updates that don't require more than 60fps
private void MainTimerEvent(Object myObject, EventArgs myEventArgs)
{
//Call frame update
UpdateInterface();
}
}
}
最佳答案
这三种方法中最快的是独立的Thread
。
您的单独线程基准测试存在根本缺陷。它是 ClockText.Invoke(MI)
。原因是 Invoke
在 GUI 更新完成之前阻塞。这非常慢。
如果您不想阻止,可以改用 BeginInvoke
,但我也不推荐这样做。
当谈到高性能时,我会强烈避免“推送”到 GUI 线程(通过 Invoke
、BeginInvoke
、SynchronizationContext
等...)。这些都以相同的方式运行 - 它们使用 Windows 消息队列,就像您的 WndProc
解决方案所做的那样。你可以淹没那个队列。这是响应用户输入和无数其他事物(如绘画消息)的同一个队列。
相反,我建议从 GUI 线程“拉取”。有很多不同的方法可以做到这一点,所以我不能在不了解更多的情况下给你写一些具体的东西。但一个简单的方法是使用 System.Windows.Forms.Timer
并经常刷新 GUI。
关于c# - 游戏逻辑的最快迭代方法 : thread, 空闲处理程序、winproc 或其他我不知道的东西?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25690359/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!