2

I have a WPF program where my model need to load a "Out-of-Proc" (.exe) COM component in order to achieve some validations when a user make an action on the UI. I'd like to inform the user that a long action will take place to let him know that the application is busy, not just frozen. But any action on the UI does occur after the COM action is completed.

I think that any COM communication should be done on the main UI thread. It eliminates any solution that would run on another thread than the main (UI) thread.

I tried many options without success:

I can't see how I can achieve a synchronous action from a model where I would need to refresh the UI. My action has a property "IsLoading" that I subscribe from my view and where I try to update the UI according to its state but it seems that is not possible in WPF ???

Any other suggestions ?

Can I use async/await and do my COM action from another thread running another dispatcher (kind of complex) and will loose required synchronicity (user need results of COM operation to continue its work) ?

Mainly for Blindy... Some clearer explications (more details about required synchronicity):

When a user click on a TreeView item, I load a grid then need to verify that data entered in the grid is still valid. To do validation, I need to load an application through COM and automate it to load a document, then parse it and verify the data in the grid (in the Model of the Grid in the view). That takes 10 seconds. If I do that on another thread, then user can do an action to select to add a new row in the grid which still depends on the same COM application loaded with the previous document. I still need to wait for the application to load. It is a synchronous action. My application depends on that COM application with its loaded document to be in valid state for user to take more actions. But I need to give user some feedback on what I’m doing (start COM app and load on document). Doing COM action on another thread just report the problem later but do not solve the fact that user need to wait that the action would complete. I think I need to (force) update my WPF app but can’t find any (twisted) way to do it.

Community
  • 1
  • 1
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
  • 2
    Out of curiosity, why should COM be called from main UI thread? – Crono Feb 03 '14 at 15:43
  • Why do you think it needs to be done in the UI thread? – Gayot Fow Feb 03 '14 at 15:43
  • As far as I know, almost all COM component are STA (single threaded apartment) and should be called from the first thread (UI) of an application... Because of CoInitialize() I think ? – Eric Ouellet Feb 03 '14 at 15:48
  • 1
    When using COM objects from a managed thread, the CLR will automatically create a single threaded apartment and marshal all calls/returns from this apartment. [See here for source](http://msdn.microsoft.com/en-us/library/5s8ee185.aspx). Just ensure you mark your background thread as STA if you are using an STA COM object. – Lukazoid Feb 03 '14 at 15:56
  • @Lukazoid, if I understand right, I can mark a worker thread as STA, then create a COM object on that thread. But is that objet will be accessible from my main thread directly or do I will have to pass through the thread that created it after. (Also, is there a way to synchronize that with my main thread ? ... otherwise action could occurs that would expect that COM action is completed when it is not) ? – Eric Ouellet Feb 03 '14 at 16:14
  • Your understanding is correct. If you create a COM object on a background STA thread, you won't be able to access it from your main thread. You'll need to use `Invoke` and friends from your main thread to synchronize with your background thread. Queues may help here as well. – Eric Brown Feb 03 '14 at 19:21
  • @Lukazoid, *When using COM objects from a managed thread, the CLR will automatically create a single threaded apartment and marshal all calls/returns from this apartment.* Not the CLR, but COM will, and this is true only if the managed thread is MTA: http://stackoverflow.com/q/21451313/1768303 – noseratio Feb 04 '14 at 05:32

3 Answers3

4

You can create and use COM objects on any thread, the marshaller will take care of running it on a background thread if your application uses the STA threading model. There's no need to funnel every call through the main UI thread.

As to your last question in comments, since you'll be running this on a background thread, you of course need to synchronize like usual with lock and Invoke the result back on the main thread when done. This is nothing specially related to COM, it's just how Windows threading works.

In short, stop using the UI thread for heavy duty, non-UI related work.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • I added more details about the required synchronicity. I believe I can't do anything on another thread. I can't see how and benefits of it... in this case. I do use threads (tasks, tpl an so on) very often but in this case... can't see benefits... See added details in question description. – Eric Ouellet Feb 03 '14 at 17:11
  • Stop fixating on a "solution" and asking for an entire operating system to be redesigned around your flawed design. You click your tree view node, disable the tree view, set a "please wait" control to visible (nice animated wheel etc) and let your thread do its job. Once done, hide the cover, re-enable the tree view and give feedback to the user (either say yay! and clear the boxes, or highlight the errors and display appropriate error messages). I'll say it again, never block the main UI thread no matter how you think it's appropriate. Ever. At all. Any reason you have is wrong. – Blindy Feb 03 '14 at 18:38
  • I agree I should not block the main thread in general. My code do lazy loading and the COM object could be accessed from many places in course of large actions. It's easy to load on another thread when it is part of a single task but when you have to modify many places and do pretty big modifications for something that could be so simple, I'm inclined to think that it should be possible to block the main thread in those specific circumstances. About flawed design... Yes it is and I would like to work on something else but there is things I don't have control over. – Eric Ouellet Feb 04 '14 at 14:27
  • Your solution is very close to Noseratio. I will probably try to implements it although it will impact my delivery time due to the large amount of works of transforming my code (many places) to async. I really think it is questionable, because I must lock 75% of the user interface anyway when I load the COM object. When I put advantages for the users of having 25% of its interface to work vs all the complexity to do it and maintain and the time to put in place... I shall say I choose the simplicity of my twisted way to update the interface :-s ! – Eric Ouellet Feb 04 '14 at 14:35
4

[UPDATE] As the OP has updated the question and specified he's using out-of-proc COM objects, the custom STA thread plumbing described below doesn't make sense. Now, a simple await Task.Run(() => { /* call the out-of-proc COM */}) is enough to keep the UI responsive. Kudos to @acelent for clarifying this point.


Recently I answered a related question: StaTaskScheduler and STA thread message pumping.

The solution was to create and use STA COM objects on a dedicated background STA thread which provides both message pumping and thread affinity for those COM objects.

I'd like to show how ThreadWithAffinityContext can be used in your case, with 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);
    }
}

The fact that we have offloaded the lengthy process _comObject.DoWork() to a separate thread doesn't automatically solve the other common UI-related problem:

How to handle the UI when the lengthy background operation is pending?

There is a number of options. E.g., you can disable the UI elements which fire NewCommand_Executed event, to avoid re-entrancy, and enable another UI element to allow the user to cancel the pending work (a Stop button). You should also provide some progress feedback, if your COM object supports that.

Alternatively, you can display a modal dialog before staring the long-running task and hide it when the task has completed. As far as the UI usability goes, modality is less desirable, but it's very easy to implement (example).

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    WOW ! Great ! I found a twisted way to do what I want and was trying to package it in a re-usable control (it works fine but it is very twisted). But one thing for sure... I definitively have to give a try to your solution! It looks sharp and sounds very promising. I will probably take me few hours (see days before answering) but in the meantime... THANKS SO MUCH !!!! To me, it look likes the best possible solution, and a great example ! – Eric Ouellet Feb 04 '14 at 14:03
  • @EricOuellet, no problem. Note, if your `ComObject` sources some events (e.g., progress events), you can handle them inside `await` as well. But you'd need to use `dispatcher.BeginInvoke` or `IProgress` pattern to propagate updates to the UI thread. – noseratio Feb 04 '14 at 14:20
  • 2 things: 1 - My COM object does not have any events and that lack give me some hard times at some places (I need to use poling, grrrr). 2 - As Eric Brown mention in comment to my question, it sounds that STA COM object created on a worker thread will need to be accessed through some artifacts from another thread (ex UI thread)??? If it's true, it sounds to add more complexity to a solution that wasn't simple already ? – Eric Ouellet Feb 04 '14 at 14:47
  • @EricOuellet, if you're going to use my solution, you'd have to access objects *always* via `await _staThread.Run(() => { /* do stuff like polling etc */})`. That's the price you pay to keep the UI thread responsive. You don't have much choice though, things like `DoEvents` is [really a Pandora's Box](http://stackoverflow.com/a/5183623/1768303). You can have multiple apartments like `_staThread`, but each COM object should belong to only one of those. – noseratio Feb 04 '14 at 14:53
  • I think I can also use marshaling but it is also a pain and a ass to use. I did it some times a go in C++ but I switched to C# to avoid to think about language specificity instead of development goal... Thanks a lot for your quick feedback. I red your link and Hans Passant answer to it. I should agree but really weighting complexity and time vs potential simplicity drive me (for the moment) to my twisted solution. I will write it down here when ready but does not mean it will be my accepted answer (just to share what I have found). – Eric Ouellet Feb 04 '14 at 15:04
  • @EricOuellet, I don't think marshalling would solve your problem though, because COM marshalling is synchronous too (unless your COM object has `BeginWork`/`EndWork`-style of API and creates its own threads). Anyhow, the solution you've came up with may suit you better if it works. – noseratio Feb 04 '14 at 15:11
  • For this example, I believe any other thread will do (i.e. MTA), since the COM object is in another process. There's no need for an STA thread. – acelent Feb 04 '14 at 17:31
  • I think it will be required. COM-STA model is the model done for UI object either in a DLL or EXE. I think the apartment model exists for the purpose of synchronizing UI access (same as C# Dispatcher->Invoke). Although the UI is in another process, you cannot write to its UI from any thread, you will have the same problem that would occur if it was possible in C#. – Eric Ouellet Feb 04 '14 at 18:17
  • @EricOuellet, the remote object's apartment needs to be STA if it runs its own UI (e.g. an Office application), not your application's proxy object(s). – acelent Feb 04 '14 at 18:56
  • @acelent, the implicit STA apartment provided by COM for MTA clients pumps only [very specific COM marshaller messages](http://stackoverflow.com/q/21451313/1768303). OTHO, some STA COM objects may rely upon full-featured pump, like in [this case](http://stackoverflow.com/questions/21211998/stataskscheduler-and-sta-thread-message-pumping). – noseratio Feb 05 '14 at 01:28
  • @Noseratio, but if the object lives in another application, none of that matters. You can just as well use a regular MTA thread from the `ThreadPool` to automate the application. It's the other application that must pump messages in its STA threads. What you say is correct for in-process servers (e.g. DLLs). This is an out-of-process server (e.g. EXEs, surrogates). – acelent Feb 05 '14 at 11:54
  • @acelent, why are your talking about a separate process (as EXE)? It the OP's case all objects, managed and unmanaged, live inside the same process. Did I miss anything? – noseratio Feb 05 '14 at 12:03
  • 1
    From the question, "To do validation, I need to load an application through COM and automate it to load a document". – acelent Feb 05 '14 at 12:18
  • If @EricOuellet indeed meant an out-of-proc COM server (rather than an in-proc STA COM server), that'd be a whole different story. I hope he can clarify this point. – noseratio Feb 05 '14 at 12:23
  • @Noseratio, note that I upvoted your answer, because you're right on about the UI part. It's just that although an STA context might do the job, it's not necessary. That is, the op can solve his problem by simply delegating to another thread, it doesn't matter if it's STA or MTA. The thing is that MTA is readily available without extra code. PS: I agree about the necessary clarification. – acelent Feb 05 '14 at 12:23
  • @Noseratio and acelent, it is an out-of-proc server, I updated my question. I read [MSDN](http://msdn.microsoft.com/en-us/library/windows/desktop/ms693344(v=vs.85).aspx) and realized that I was wrong. It sounds that COM will synchronize my access to the server. That simplify a lot the management of the COM object (by any threads). But I left with the fact that I should lock so many things because they depends on a valid COM object loaded with appropriate document. I'm trying to see how I can do it simply (without adding verification everywhere). – Eric Ouellet Feb 05 '14 at 16:53
  • Ho yeah!, I can just pop a modal window!... But the loading can come from many path (due to lazy loading)... How to solve that ? I need to break where I am (when I realize that I need to load) then continue the flow of execution where it left. – Eric Ouellet Feb 05 '14 at 16:59
  • OK... I will try. I load as soon as I know I will have too then pop a modal dialog when waiting the COM object... That's mainly Blindy solution too... Sometimes I took so long to understand !!! :-s – Eric Ouellet Feb 05 '14 at 17:06
  • Then @acelent is *absolutely right*, you don't need any custom STA thread plumbing I proposed. Just use the standard `await Task.Run(() => { /* call your out-of-proc COM */})` to keep the UI responsive. – noseratio Feb 05 '14 at 19:56
  • @Noseratio, this last comment doesn't hold for me. I get an error "this thread is not STA" when I try invoking COM with that statement. – Alan Baljeu Dec 12 '14 at 22:49
  • @AlanBaljeu, note the `/* call your out-of-proc COM */` comment. Your error message suggests you're trying to call an *in-proc* STA COM object rather than an out-of-proc COM proxy. The former requires thread affinity. – noseratio Dec 12 '14 at 22:57
  • I'm dealing with a .net wrapper library that connects to a COM server. I don't know the internals of the library (though I could look). – Alan Baljeu Dec 12 '14 at 23:08
  • @AlanBaljeu, check the registry for the COM server, whether it is InProcServer32 or LocalServer32. – noseratio Dec 12 '14 at 23:10
  • It appears to be LocalServer32 – Alan Baljeu Dec 12 '14 at 23:35
  • @Noseratio, if I understand this correctly, the solution for me is to download and install the TPL library, and then put the code above into my program to use TPL. – Alan Baljeu Dec 13 '14 at 01:20
  • @AlanBaljeu, you may want to ask it as a separate question, including some relevant code. – noseratio Dec 13 '14 at 01:40
  • Okay, my question: http://stackoverflow.com/questions/27444483/how-to-have-an-async-connection-to-com-with-a-wpf-interface/27444814 – Alan Baljeu Dec 13 '14 at 03:12
0

I used this once for WPF so as to force the screen to re-Paint : I used auto-translation from VB so I hope it's correct

private Action EmptyDelegate = () => { };
[System.Runtime.CompilerServices.Extension()]
public void Refresh(UIElement uiElement)
{
    uiElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, EmptyDelegate);
}
  • I think somebody give you -1. It's not me. Sorry. Thanks for the try. I could use BeginInvoke with Background priority but it will prevent my code from being synchronous (user will be able to do some action on the screen before my action will occur) – Eric Ouellet Feb 03 '14 at 15:51
  • 1
    Also, it happen from my model. When I click on the screen, first time it do the long action. After, no action required. I know I can check my model state before calling my model action and show IsBusy screen if needed but it sounds to me bad design. If I change my model, I would need to change my view in accordance (the logic of deciding if I'm busy should be decided in the model). But honestly, I think that I will try your (little bit modified) solution and will do a little bad programming exception to see if it works. It will not be perfect but I think it should works! – Eric Ouellet Feb 03 '14 at 16:01
  • Thank, I tried some solution related to your suggestion (like the DoEvents from Microsoft : http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.pushframe(v=vs.110).aspx) without success. I have not found the exact reason why it don't work. – Eric Ouellet Feb 10 '14 at 16:14