[英]Running a WPF control in another thread
我在我的项目中使用的可视化控件来自我没有源代码的库。
更新时间太长(大约 200 毫秒)以同时在屏幕上显示这些控件中的三个以获得良好的 UI 响应。 (我可能需要同时更新所有三个,这让我的 UI 在他们都在思考时卡住了 ~600 毫秒)。
我已经阅读了一些关于 TaskScheduler 的文章,并且开始研究并行任务功能,作为在它们自己的线程中运行这些控件中的每一个的一种方式。 该平台将是多核的,所以我想利用同时处理的优势。
问题是我什至不知道我不知道如何 go 关于这个,虽然..
在 WPF 中,是否有合适的设计模式可以在与主 UI 线程不同的线程中运行控件?
具体来说:它是第三方 map 控件,当给定新位置或缩放级别时,重绘所需的时间太长(~200 毫秒)。 也许其中三个以最大 4Hz 的频率更新 - 显然他们不会跟上..
我已经将 WPF 控件封装在一个用户控件中,并且需要在它自己的线程中运行每个实例,同时仍然捕获用户输入(例如鼠标点击)。
更新:虽然我正在寻找解决方案,但到目前为止我已经实施了以下内容。
我的主 (UI) 线程生成一个线程,该线程创建一个新的 window,其中包含有问题的控件,并将其定位在正确的 position 中(这样它看起来就像是一个普通控件)。
_leftTopThread = new Thread(() =>
{
_topLeftMap = new MapWindow()
{
WindowStartupLocation = WindowStartupLocation.Manual,
Width = leftLocation.Width,
Height = leftLocation.Height,
Left = leftLocation.X,
Top = leftLocation.Y,
CommandQueue = _leftMapCommandQueue,
};
_topLeftMap.Show();
System.Windows.Threading.Dispatcher.Run();
});
_leftTopThread.SetApartmentState(ApartmentState.STA);
_leftTopThread.IsBackground = true;
_leftTopThread.Name = "LeftTop";
_leftTopThread.Start();
其中CommandQueue
是一个线程安全的 BlockingCollection队列,用于向 map 发送命令(移动位置等)。
现在的问题是我可以
System.Windows.Threading.Dispatcher.Run()
有用户输入我不能旋转等待命令,因为它会占用我所有的线程 CPU!
是否可以阻止并让事件消息泵工作?
好吧,我有一个可行的方法 - 但它可能不是最优雅的..
我有一个 window,其中包含我在 XAML 中的第三方(慢速呈现)控件。
public partial class MapWindow : Window
{
private ConcurrentQueue<MapCommand> _mapCommandQueue;
private HwndSource _source;
// ...
}
我的主 (UI) 线程在一个线程上构造并启动这个 window:
_leftTopThread = new Thread(() =>
{
_topLeftMap = new MapWindow()
{
WindowStartupLocation = WindowStartupLocation.Manual,
CommandQueue = _leftMapCommendQueue,
};
_topLeftMap.Show();
System.Windows.Threading.Dispatcher.Run();
});
_leftTopThread.SetApartmentState(ApartmentState.STA);
_leftTopThread.IsBackground = true;
_leftTopThread.Name = "LeftTop";
_leftTopThread.Start();
然后我在线程中获得 window 的句柄(在它初始化之后):
private IntPtr LeftHandMapWindowHandle
{
get
{
if (_leftHandMapWindowHandle == IntPtr.Zero)
{
if (!_topLeftMap.Dispatcher.CheckAccess())
{
_leftHandMapWindowHandle = (IntPtr)_topLeftMap.Dispatcher.Invoke(
new Func<IntPtr>(() => new WindowInteropHelper(_topLeftMap).Handle)
);
}
else
{
_leftHandMapWindowHandle = new WindowInteropHelper(_topLeftMap).Handle;
}
}
return _leftHandMapWindowHandle;
}
}
.. 在将命令放入与线程 window 共享的线程安全队列后:
var command = new MapCommand(MapCommand.CommandType.AircraftLocation, new object[] {RandomLatLon});
_leftMapCommendQueue.Enqueue(command);
..我让它知道它可以检查队列:
PostMessage(LeftHandMapWindowHandle, MapWindow.WmCustomCheckForCommandsInQueue, IntPtr.Zero, IntPtr.Zero);
window 可以收到我的消息,因为它已经挂接到 window 的消息中:
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_source = PresentationSource.FromVisual(this) as HwndSource;
if (_source != null) _source.AddHook(WndProc);
}
..然后它可以检查:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) //
{
// Handle messages...
var result = IntPtr.Zero;
switch (msg)
{
case WmCustomCheckForCommandsInQueue:
CheckForNewTasks();
break;
}
return result;
}
..然后在线程上执行!
private void CheckForNewTasks()
{
MapCommand newCommand;
while (_mapCommandQueue.TryDequeue(out newCommand))
{
switch (newCommand.Type)
{
case MapCommand.CommandType.AircraftLocation:
SetAircraftLocation((LatLon)newCommand.Arguments[0]);
break;
default:
Console.WriteLine(String.Format("Unknown command '0x{0}'for window", newCommand.Type));
break;
}
}
}
就这么简单.. :)
我也一直在研究这个问题,我能找到的最相关的信息是在这篇博文中(但我还没有测试过):
http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
它在 UI 线程上创建一个 HostVisual,然后启动一个后台线程,创建一个 MediaElement,将其放入 VisualTarget(指向 HostVisual)中,并将其全部放入我们的 hacky VisualTargetPresentationSource 中。
此方法的问题在于,显然用户将无法与在新线程中运行的控件进行交互。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.