11

I've just "earned" the privilege to maintain a legacy library coded in C# at my current work.

This dll:

  • Exposes methods for a big legacy system made with Uniface, that has no choice but calling COM objects.
  • Serves as a link between this legacy system, and another system's API.
  • Uses WinForm for its UI in some cases.

More visually, as I understand the components :

*[Big legacy system in Uniface]* ==[COM]==> [C# Library] ==[Managed API]==> *[Big EDM Management System]*

The question is: One of the methods in this C# Library takes too long to run and I "should" make it asynchronous!

I'm used to C#, but not to COM at all. I've already done concurrent programming, but COM seems to add a lot of complexity to it and all my trials so far end in either:

  • A crash with no error message at all
  • My Dll only partially working (displaying only part of its UI, and then closing), and still not giving me any error at all

I'm out of ideas and resources about how to handle threads within a COM dll, and I would appreciate any hint or help.

So far, the biggest part of the code I've changed to make my method asynchronous :

// my public method called by the external system
public int ComparedSearch(string application, out string errMsg) {
  errMsg = "";
  try {
    Action<string> asyncOp = AsyncComparedSearch;
    asyncOp.BeginInvoke(application, null, null);
  } catch (ex) {
    // ...
  }
  return 0;
}

private int AsyncComparedSearch(string application) {
  // my actual method doing the work, that was the called method before
}

Any hint or useful resource would be appreciated. Thank you.

UPDATE 1:

Following answers and clues below (especially about the SynchronizationContext, and with the help of this example) I was able to refactor my code and making it to work, but only when called from another Window application in C#, and not through COM. The legacy system encounters a quite obscure error when I call the function and doesn't give any details about the crash.

UPDATE 2:

Latest updates in my trials: I managed to make the multithreading work when the calls are made from a test project, and not from the Uniface system. After multiple trials, we tend to think that our legacy system doesn't support well multithreading in its current config. But that's not the point of the question any more :)

Here is a exerpt of the code that seems to work:

string application;
SynchronizationContext context;

// my public method called by the external system
public int ComparedSearch(string application, out string errMsg) {
    this.application = application;
    context = WindowsFormsSynchronizationContext.Current;
    Thread t = new Thread(new ThreadStart(AsyncComparedSearchAndShowDocs));
    t.Start();
    errMsg = "";
    return 0;
}

private void AsyncComparedSearch() {
    // ANY WORK THAT AS NOTHING TO DO WITH UI
    context.Send(new SendOrPostCallback(
        delegate(object state)
        {
            // METHODS THAT MANAGE UI SOMEHOW
        }
    ), null);
}

We are now considering other solutions than modifying this COM assembly, like encapsulating this library in a Windows Service and creating an interface between the system and the service. It should be more sustainable..

David
  • 270
  • 1
  • 11
  • 3
    +1 for this well organized question. – Bedir Yilmaz Mar 11 '13 at 14:09
  • Can you provide a really small sample that will crash? I'm not really sure what exactly you are doing to make it crash :) (If it's your fault, or if it's the legacy system that can't handle threading) – Onkelborg Mar 11 '13 at 15:31
  • Are you saying that the exception is never caught or that it doesn't contain any message at all? – Zdeslav Vojkovic Mar 11 '13 at 15:47
  • Your `try/catch` block should go into `AsyncComparedSearch` method as this is what is exectued on another thread. As you are not blocking. How does your app know when the COM call is done? Do you update GUI in `AsyncComparedSearch`? – Zdeslav Vojkovic Mar 11 '13 at 15:54
  • Arbitrarily throwing a thread into a large chunk of code is never not a problem. Particularly with COM, it cares about threading. Big Red Flags are also a missing call to EndInvoke(), a method that returns an int whose value won't be used and an `out string` that probably won't be set. – Hans Passant Mar 11 '13 at 17:20
  • I know the `try/catch` block is not very useful for my problem. It's a remain of the copy/paste, because I tried other ways to do the asynchronous call, and I had to exclude this from the real problem. I'll try to edit the question with more details, but the code behind `AsyncComparedSearch` is enormous and I should put only the interesting parts here. Thank you – David Mar 12 '13 at 08:56
  • @HansPassant, you're right about the out string and the int, but I try to avoid modifying the signature of the called function for now. – David Mar 12 '13 at 09:01
  • @ZdeslavVojkovic, no exception is thrown in the `AsyncComparedSearch` function. The library is craching (`myprogram.vshost.exe` stopped working) on a call to the EDM Management system. all I can find in my windows events is some kind of error related to `mfc100u.dll`. Which make me think of some kind GUI problem. (Like you wrote in your answer below btw) – David Mar 12 '13 at 09:14
  • Maybe its marshalling? you've just got int and string, what type/size of those? Look at the 'MarshalAs' attribute. I've just had to use it in an async COM that returns a bool, but the callee expected a VB6 Variant_Bool. I don't think threads per se are the issue. – cjb110 Mar 12 '13 at 09:29
  • you say that it "crashes on a call to the EDM Management" - according to your diagram, this is not a COM call, it is managed call, is that right? – Zdeslav Vojkovic Mar 12 '13 at 09:32
  • Well, the crash seems to be resolved by.... replacing the [STAThread] by an [MTAThread] attr. Now I have some form of GUI appearing, but with weird behavior.. so still not solved entirely. Your clue about the GUI seems to be the right one. – David Mar 12 '13 at 10:51
  • Because of the bi-directional arrows in your diagram, I cannot tell where the call chain starts, who creates who, etc. Can you clarify? – tcarvin Mar 14 '13 at 14:47
  • You're right @tcarvin, I changed the arrows' directions. – David Mar 15 '13 at 10:04
  • ...and added latest updates in our development – David Mar 15 '13 at 10:20

1 Answers1

3

It is hard to tell without knowing more details, but there are few issues here.

You execute the delegate on another thread via BeginInvoke but you don't wait for it. Your try\catch block won't catch anything as it has already passed while the remote call is still being executed. Instead, you should put try\catch block inside AsyncComparedSearch.

As you don't wait for the end of the execution of remote method (EndInvoke or via callback) I am not sure how do you handle the results of the COM call. I guess then that you update the GUI from within AsyncComparedSearch. If so, it is wrong, as it is running on another thread and you should never update GUI from anywhere but the GUI thread - it will most likely result with a crash or other unexpected behavior. Therefore, you need to sync the GUI update work to GUI thread. In WinForms you need to use Control.BeginInvoke (don't confuse it with Delegate.BeginInvoke) or some other way (e.g. SynchronizationContext) to sync the code to GUI thread. I use something similar to this:

private delegate void ExecuteActionHandler(Action action);

public static void ExecuteOnUiThread(this Form form, Action action)
{
  if (form.InvokeRequired) { // we are not on UI thread
    // Invoke or BeginInvoke, depending on what you need
    form.Invoke(new ExecuteActionHandler(ExecuteOnUiThread), action);
  }
  else { // we are on UI thread so just execute the action
    action();
  }
}

then I call it like this from any thread:

theForm.ExecuteOnUiThread( () => theForm.SomeMethodWhichUpdatesControls() );

Besides, read this answer for some caveats.

Community
  • 1
  • 1
Zdeslav Vojkovic
  • 14,391
  • 32
  • 45
  • Thank you for your reply and link. The logic in `AsyncComparedSearch` is quite complex and does not fit in the post. I have multiple `try/catch` block in it and they don't catch a thing. I'll try to put the details that matter in an edit of the question. And you're right about the handle of the GUI, this is handled from within `AsyncComparedSearch`. I'll search in that direction today and let you know. – David Mar 12 '13 at 09:08
  • I have added a note about synchronization to the answer – Zdeslav Vojkovic Mar 12 '13 at 09:14
  • Following the clues given in your answer and the link about the `SynchronizationContext`, I was able to make my function work correctly, but only when the call is made from another C# Project, and not when the call is made from the legacy system through COM. At least, there's a positive evolution here. – David Mar 12 '13 at 14:34
  • 1
    Marked it as "the answer" because it helped a lot to understand the multithreading problem, even if there's other issues in our architecture... – David Mar 15 '13 at 10:23