Microstation 中实现非阻塞进度条

在进行 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
/// <summary>
/// 打开窗体
/// </summary>
/// <param name="messageText"></param>
/// <returns></returns>
[DllImport("ustation.dll", CharSet = CharSet.Unicode)]
public extern static IntPtr mdlDialog_completionBarOpen(string messageText);
/// <summary>
/// 更新窗体内容
/// </summary>
/// <param name="dialog"></param>
/// <param name="messageText"></param>
/// <param name="percent"></param>
[DllImport("ustation.dll", CharSet = CharSet.Unicode)]
public extern static void mdlDialog_completionBarUpdate(IntPtr dialog, string messageText, int percent);
/// <summary>
/// 显示消息
/// </summary>
/// <param name="dialog"></param>
/// <param name="messageText"></param>
[DllImport("ustation.dll", CharSet = CharSet.Unicode)]
public extern static void mdlDialog_completionBarDisplayMessage(IntPtr dialog, string messageText);
/// <summary>
/// 关闭进度条
/// </summary>
/// <param name="dialog"></param>
[DllImport("ustation.dll", CharSet = CharSet.Unicode)]
public extern static void mdlDialog_completionBarClose(IntPtr dialog);
#endregion

/// <summary>
/// 更新进度条
/// 总进度为 100
/// </summary>
/// <param name="message"></param>
/// <param name="progress">进度值,默认 0-100</param>
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)