[更新]由于 OP 更新了问题并指定他正在使用进程外COM 对象,因此下面描述的自定义 STA 线程管道没有意义。现在,一个简单await Task.Run(() => { /* call the out-of-proc COM */})
的就足以保持 UI 响应。感谢@acelent澄清了这一点。
最近回答了一个相关的问题:StaTaskScheduler and STA thread message pumping。
解决方案是在专用后台 STA 线程上创建和使用 STA COM 对象,该线程为这些 COM 对象提供消息泵送和线程关联。
我想展示如何ThreadWithAffinityContext
在您的情况下使用async/await
:
dynamic _comObject = null;
ThreadWithAffinityContext _staThread = null;
// Start the long-running task
Task NewCommandHandlerAsync()
{
// create the ThreadWithAffinityContext if haven't done this yet
if (_staThread == null)
_staThread = new ThreadWithAffinityContext(
staThread: true,
pumpMessages: true);
// create the COM Object if haven't done this yet
if (_comObject == null)
{
await _staThread.Run(() =>
{
// _comObject will live on a dedicated STA thread,
// run by ThreadWithAffinityContext
_comObject = new ComObject();
}, CancellationToken.None);
}
// use the COM object
await _staThread.Run(() =>
{
// run a lengthy process
_comObject.DoWork();
}, CancellationToken.None);
}
// keep track of pending NewCommandHandlerAsync
Task _newCommandHandler = null;
// handle a WPF command
private async void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
// avoid re-entrancy (i.e., running two NewCommandHandlerAsync in parallel)
if (_newCommandHandler != null)
throw new InvalidOperationException("One NewCommandHandlerAsync at a time!");
try
{
await _newCommandHandler = NewCommandHandlerAsync();
}
finally
{
_newCommandHandler = null;
}
}
catch (Exception ex)
{
// handle all exceptions possibly thrown inside "async void" method
MessageBox.Show(ex.Message);
}
}
我们将冗长的进程卸载_comObject.DoWork()
到单独的线程这一事实并不能自动解决其他常见的 UI 相关问题:
当冗长的后台操作挂起时如何处理 UI?
有多种选择。例如,您可以禁用触发NewCommand_Executed
事件的 UI 元素,以避免重新进入,并启用另一个 UI 元素以允许用户取消待处理的工作(一个Stop
按钮)。如果您的 COM 对象支持,您还应该提供一些进度反馈。
或者,您可以在启动长时间运行的任务之前显示一个模式对话框,并在任务完成后将其隐藏。就 UI 可用性而言,模态不太理想,但它很容易实现(示例)。