在进行 Microstation
二开时,若在代码执行耗时任务,则可能会导致界面出现假死的情况,此时窗体无法拖动、进度条也无法更新。
由于 Microstation
的非线程安全问题,代码必须在主线程上执行,但进度条位于主线程上,因此进度必定会卡死,这是一个矛盾的问题。
有两种方法来解决这个问题。
为什么假死
首先简要解释下,为什么为假死。
Windows 的窗体靠消息循环来更新 UI,UI 位于主线程上。而 Microstation
的 SDK 是非线程安全的,因此所有的任务都是在主线程上执行。
当在主线程执行耗时计算时,主线程将被这个任务给占用,从而导致消息无法被处理,因此造成界面假死的情况。
原生进度条方案
上面提到,卡死是由于窗体无法处理消息循环导致的,因此我们可以在进度变动时,让系统先处理一下消息,然后再继续执行代码。
通过调用 System.Windows.Forms.Application.DoEvents();
来让线程处理窗体消息。
详细参见:Application.DoEvents
方法 (System.Windows.Forms) | Microsoft Learn
部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #region P/Invoke [DllImport("ustation.dll", CharSet = CharSet.Unicode)] public extern static IntPtr mdlDialog_completionBarOpen(string messageText); [DllImport("ustation.dll", CharSet = CharSet.Unicode)] public extern static void mdlDialog_completionBarUpdate(IntPtr dialog, string messageText, int percent); [DllImport("ustation.dll", CharSet = CharSet.Unicode)] public extern static void mdlDialog_completionBarDisplayMessage(IntPtr dialog, string messageText); [DllImport("ustation.dll", CharSet = CharSet.Unicode)] public extern static void mdlDialog_completionBarClose(IntPtr dialog); #endregion
public static void Update(string message, int progress) { mdlDialog_completionBarUpdate(_dialogPtr, message, progress); Application.DoEvents(); }
|
具体效果如下:
虽然很方便,但是也有一些缺点:
参考:
多线程方案
前面提到,由于进度条位于主线程,才导致它被阻塞,那么若能将其放到另一个线程里,与主线程分离,也就不会出现阻塞的情况了。
要实现将 UI
放到另外的线程,只需要注意一个关键点,就是需要将线程设置成
STA
模式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| public void StartProgress() { var thread = new Thread(() => { _window = new ProgressWindow(this); _window.ShowDialog(); }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); }
|
效果如下:
STA 模式可自行百度了解:什么是单线程单元(STA)什么是多线程单元(MTA)
源代码
本文示例代码已开源,地址:AwsomeBentley/Examples/CSharpBentley/CSharpMicrostation/ProgressExamples
at master · GalensGan/AwsomeBentley (github.com)