C# DoEvents vs其他任何东西-->;用于长时间COM操作
我有一个WPF程序,在这个程序中,我的模型需要加载一个“outproc”(.exe)COM组件,以便在用户对UI执行操作时实现一些验证。我想通知用户,将进行一个长时间的操作,让他知道应用程序正忙,而不仅仅是冻结。但是UI上的任何操作都会在COM操作完成后发生 我认为任何COM通信都应该在主UI线程上完成。它消除了在主(UI)线程以外的其他线程上运行的任何解决方案 我尝试了很多选择,但都没有成功:C# DoEvents vs其他任何东西-->;用于长时间COM操作,c#,wpf,multithreading,com,doevents,C#,Wpf,Multithreading,Com,Doevents,我有一个WPF程序,在这个程序中,我的模型需要加载一个“outproc”(.exe)COM组件,以便在用户对UI执行操作时实现一些验证。我想通知用户,将进行一个长时间的操作,让他知道应用程序正忙,而不仅仅是冻结。但是UI上的任何操作都会在COM操作完成后发生 我认为任何COM通信都应该在主UI线程上完成。它消除了在主(UI)线程以外的其他线程上运行的任何解决方案 我尝试了很多选择,但都没有成功: 我看不出如何从需要刷新UI的模型中实现同步操作。 我的操作有一个属性“IsLoading”
如果我在另一个线程上这样做,那么用户可以执行一个操作来选择在网格中添加一个新行,这仍然依赖于与上一个文档一起加载的同一个COM应用程序。我仍然需要等待应用程序加载。这是一个同步动作。我的应用程序依赖于COM应用程序及其加载的文档处于有效状态,以便用户执行更多操作。但我需要给用户一些关于我正在做什么的反馈(启动COM应用程序并加载文档)。在另一个线程上执行COM操作只是稍后报告问题,但不能解决用户需要等待操作完成的事实。我想我需要(强制)更新我的WPF应用程序,但找不到任何(扭曲的)方法来更新它。我曾经在WPF中使用过这个,以便强制屏幕重新绘制: 我使用了VB的自动翻译,所以我希望它是正确的
private Action EmptyDelegate = () => { };
[System.Runtime.CompilerServices.Extension()]
public void Refresh(UIElement uiElement)
{
uiElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, EmptyDelegate);
}
您可以在任何线程上创建和使用COM对象,如果您的应用程序使用STA线程模型,封送器将负责在后台线程上运行COM对象。没有必要通过主UI线程来实现每次调用 至于您在评论中的最后一个问题,由于您将在后台线程上运行此程序,因此您当然需要像往常一样在主线程上同步
lock
和Invoke
结果。这与COM无关,只是Windows线程的工作方式
简而言之,停止将UI线程用于繁重的、与UI无关的工作。[UPDATE]由于OP已更新问题并指定他使用的是进程外对象,下面描述的自定义STA线程管道没有意义。现在,一个简单的
wait Task.Run(()=>{/*调用out-of-proc-COM*/})
就足以让UI保持响应。澄清这一点值得称赞。
最近我回答了一个相关的问题: 其目的是在专用的后台STA线程上创建和使用STA COM对象,该线程为这些COM对象提供消息泵送和线程关联 我想展示如何在您的案例中使用
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);
}
}
我们将冗长的进程\u comObject.DoWork()
卸载到一个单独的线程中,这一事实并不能自动解决其他与UI相关的常见问题:
当冗长的后台操作挂起时,如何处理UI?
有很多选择。例如,您可以禁用触发NewCommand\u Executed
事件的UI元素,以避免重新进入,并启用另一个UI元素以允许用户取消挂起的工作(一个停止
按钮)。如果COM对象支持,您还应该提供一些进度反馈
或者,您可以在启动长时间运行的任务之前显示一个模式对话框,并在任务完成后将其隐藏。就用户界面的可用性而言,模态不太理想,但它很容易实现()。出于好奇,为什么要从主用户界面线程调用COM?为什么你认为它需要在用户界面线程中完成?据我所知,几乎所有的COM组件都是STA(单线程单元),应该从第一个线程(UI)调用一个应用程序的。。。因为CoInitialize(),我认为?当从托管线程使用COM对象时,CLR将自动创建一个单线程单元,并封送来自该单元的所有调用/返回。如果使用STA COM对象,请确保将背景线程标记为STA。@Lukazoid,如果我理解正确,我可以将工作线程标记为STA,然后在该线程上创建COM对象。但是objet可以直接从我的主线程访问,或者我必须通过创建它的线程。(还有,是否有一种方法可以将其与我的主线程同步?…否则可能会发生预期COM操作在未完成时完成的操作)?我想有人会给你-1。不是我。很抱歉谢谢你的尝试。我可以使用具有后台优先级的BeginInvoke,但它会阻止我的代码同步(用户可以在我的操作发生之前在屏幕上执行一些操作),而且,它也会发生在我的模型中。当我点击屏幕时,它第一次做长动作。之后,无需采取任何行动。我知道我可以在调用模型之前检查模型状态