0

I'm working on a big legacy project with a lot of libraries and layers. I added some functionality to a common library and declared some async methods. In order to implement the asynchronous approach end-to-end I had to refactor some existing methods to convert them to async. This lead to a chain of modifications, which have also involved changing a method call in an old webform (aspx). The method called by the webform is now async but, unfortunately, I can't use "async" or "await" inside the webform code-behind class because there are COM references within the webform and therefore it has to have "async=false".

In the webform there is a method which updates the UI with the output from the method which is now async. I have tried calling my async method using Task.Run.

This is how it used to be:

var result = mySyncronousMethod();
updateUI(result);

This is how it is now:

var result = Task.Run(async () => await commonLib.myAsyncMethod()).Result;
updateUI(result);

Unfortunately, the code path that is triggered by myAsyncMethod() includes some old COM calls somewhere along the lines. When the code hits these, it errors with "Object reference..." exceptions. Interestingly, if I debug the COM code (using VB6 Studio - ... I know, it's ancient!) it all works perfectly. I believe it's because of the late binding.

If I just call:

Task.Run(async () => await myAsyncMethod());

and don't update the UI, it all works. The async method runs and does what it needs to do with no errors. I just can't use the output to do anything in the UI, because the UI thread continues and completes the page lifecycle (as expected). The error appears only if I call ".Result".

I spent a lot of time trying to figure out how to do this. In the end I refactored the code to remove the COM references altogether, replacing them with .NET code. This solved all my problems. From the web form I can now use:

var result = Task.Run(async () => await commonLib.myAsyncMethod()).Result;
updateUI(result);

My question is: is there a way in which this can work without having to remove the COM references?

I hope this all makes sense. Any ideas would be greatly appreciated.


Update 18/09/2019

I tried reproducing this issue in this repo https://github.com/erionpc/AsyncCOM

It's really hard to isolate the code but this is the best I could do. However, the issue can't be reproduced in this sample solution. The only difference is that I had to add a key in the web.config app settings

<add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" />

otherwise I would get an error. The original web form doesn't have this.

erionpc
  • 368
  • 3
  • 15
  • Putting the COM threading issues apart (depends on current thread's COM apartment type, COM object threading model, what piece of code creates the COM object, etc.), what's the benefit of switching threads and await the result if you don't use non CPU bound (IO or other processing unit bound) tasks? (https://stackoverflow.com/questions/37419572/if-async-await-doesnt-create-any-additional-threads-then-how-does-it-make-appl) Just keep these methods synchronous. – Simon Mourier Sep 16 '19 at 17:18
  • Thanks for your comment @Simon. The point in making these methods asynchronous is that there are other code paths in the application that are asynchronous end-to-end. In this specific instance, there is a web api project which calls an async method defined in a common class library. The web form I mentioned in my post is not the only thing that calls the asynchronous method. In fact, I had no intention of modifying that part of the application at all. I had to do that in order to make fully asynchronous code paths for other parts of the application. – erionpc Sep 16 '19 at 22:08
  • The thing is you should try to keep this code portion synchronous, even surrounded by asynchronous code. Turning any COM code path asynchronous is far from just adding some async await task keywords and hoping it'll work. It may even be impossible, as I said it heavily depends on details, and we don't have enough. – Simon Mourier Sep 17 '19 at 06:07
  • Thanks. That's exactly what I would like to do. I would like to keep this legacy code path synchronous but have other (newer) code paths which are asynchronous. I just need to be able to consume an asynchronous method synchronously from this web form, without having to remove the COM references from the code path. The presence of COM doesn't preclude the use of async. The problems arise (in my case) when these async methods are consumed synchronously. As I mentioned above, if I just call `Task.Run(async () => await myAsyncMethod());` everything works fine. – erionpc Sep 17 '19 at 07:39
  • I'm just not able to do anything with the result because I can't get back to the UI thread. What details would you like me to provide? – erionpc Sep 17 '19 at 07:44
  • A small reproducing sample. – Simon Mourier Sep 17 '19 at 07:47
  • I updated my post and added a sample solution. However, I can't reproduce the problem :(. Sorry, it took a few hours just putting this together. – erionpc Sep 18 '19 at 12:32
  • I've tested your project but I'm unsure what it's supposed to do :-) Few remarks: AspCompat is a game changer. Also avoid calling .Result on a task once you've gone async. Also make sure you use ConfigureAwait(false) in library code: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – Simon Mourier Sep 18 '19 at 14:45
  • :) It's supposed to throw an error due to COM instantiation upon click on the button like the real application on which I'm working, but it doesn't. I tried to set all the project properties exactly in the same way in this sample solution. AspCompat is set to true in the original webform. When I ran this sample solution for the first time I got an error due to AspCompat=true and apartmentThreading=true. I had to add to avoid this. The original web form doesn't have this. Thanks for looking. – erionpc Sep 18 '19 at 15:21
  • I just updated the sample solution to mirror the convolutedness of the original application. The COM class also calls the .NET project which is com visible. However, this still doesn't reproduce the original problem :). – erionpc Sep 18 '19 at 15:59
  • That's exactly what I meant when I said "it heavily depends on details"... I can't start it, it tells me "Cannot create ActiveX component on COM.Test" which is normal since there's no COM.Test progid in the project. – Simon Mourier Sep 18 '19 at 16:04
  • Ah, you need to publish the web app as an IIS application and then register COM.Test for it to work using regsvr32, then enable 32bit apps on the app pool. That's how the original works. I did it by compiling the DLL using VB6 studio, which registers it automatically. Thanks. You've got to love COM :) – erionpc Sep 18 '19 at 19:34
  • The COM folder in the repo contains the VB6 project. – erionpc Sep 18 '19 at 19:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/199649/discussion-between-erionpc-and-simon-mourier). – erionpc Sep 18 '19 at 19:42

1 Answers1

0

I finally reached a conclusion on this. The way forward is: move the COM references from the web form to one of the libraries, so the webform can have async=true. I can then use "async" and "await" to call "myAsyncMethod()". This is the simplest and quickest solution. Going forward, in similar situations I may have to create template methods, overloads and async alternatives (e.g. My method() and MyMethodAsync()) so that things can be done asynchronously and synchronously. If anyone has any better ideas, please let me know.

erionpc
  • 368
  • 3
  • 15