How to run method from assembly loaded to references, in new AppDomain - c#

Recently i discover that running few instances of method compiled to .exe is faster than running the same method in f.e. few new Tasks. I don't know if that applies to every method, but it does to getting data from API.
I was searching internet to find answer how to manage that. I got answers to try run method in new appDomains. So i create .exe assembly with methods that i want to be run (it is Console application). I Load it by right click on References -> Add Reference. I could easily access that method by exeName.ClassName.Method(params). The thing is that I don't know how to run this methods in new appDomains. Every answer that i found in web was with loaded assembly by path.
I will also be very happy for answers other than creating AppDomain. I just want to pass data to this method and get results.
TL;DR: Method run in Parallel.For(0,4,i=> method()) works slower than run the same method in 4 instances of compiled .exe file.

You could use a multi process architecture using IPC protocol or host your methods inside different domains. In both situations i recommend .net remoting over wcf because you would write almost the same code for both aproaches and because for talking to a class found in another app domain hosted in the same process, .net remoting is the only way (sadly for many devs but not for me). BUT I am almost sure that generally this would NOT be more faster than just creating some threads and calling them asinchronous. Inter domains / process communication must rely on message serialization/ deserialization that adds huge overhead, specially if the method call itself is very light.

After some researching and asking i found solution:
var ad = AppDomain.CreateDomain("mydomain");
ad.DoCallBack(() =>
{
//stuff to do
}
Probably there'll be some issues with passing data to new AppDomain. Easiest way for me is:
ad.SetData("key", value);
and to retrive in AppDomain:
var value = (valueType)AppDomain.CurrentDomain.GetData("key");

Related

How to best deploy Sentry in cross-assembly environment?

So we built this library/framework thing full of code related to business processes and common elements that are shared across multiple applications (C#, .Net 4.7.1,WPF, MVVM). Our Logging stuff is all set up through this framework so naturally it felt like the best place for Sentry. All the references in our individual applications are manually pointed to the dlls the folder where our shared library thingy installs itself. So far so good.
When we set up Sentry initially everything seemed to work great. We do some updates and errors seem to be going way down. That's cause we are awesome and Sentry helped us be more awesome, right? Nope! Well I mean kind of.
The scope is being disposed of so we are no longer getting Unhandled exceptions. We didn't notice at first because we are still getting sentry logs when we are handling errors through our Logging.Log() method. This logging method calls SentrySdk.Init() which I suspect is disposing the client in the executing assembly.
We also started using Sentry for some simple Usage tracking by spinning up a separate project in Sentry called Usage-Tracker and passing a simple "DoThingApplication has been launched" with an ApplicationName.UsageTracker Enum as a parameter to our Logging method.
Question: What is a good way to handle this where my setup can have a Sentry instance that wraps my using(sentryClientStuff){ ComposeObjects(); } and still have my logging method look for the existing client and use it if it exists?
Caveats:
I believe before any of this happens we still need to make a call to send a Sentry log to our UsageTracker.
I would like to pass in as few options as possible if I'm setting up the Sentry Client/Scope in our shared library. Maybe Release and Environment. Maybe check tags for Fingerprint and set it in the Log method.
I'm open to new approaches to any of this.
Some related thoughts
Maybe there is a better way to handle references that could solve both this and some other pains of when they have become mismatched between client and shared framework/library thing
Maybe the answer can be found through adding some Unit Tests but I could use a Sentry specific example or a nudge there because I don't know a muc about that.
Maybe there is a way to use my shared library to return a Sentry Client or Scope that I could use in my client assembly that would not be so fragile and the library could somehow also use it.
Maybe there is a better solution I can't conceive because I'm just kind of an OK programmer and it escapes me. I'm open to any advice/correction/ridicule.
Maybe there is a smarter way to handle "Usage-Tracker" type signals in Sentry
Really I want a cross-assembly singleton kind of thing in practice.
There are really many things going on here. Also without looking at any code it's hard to picture how things are laid out. There's a better chance you can get the answer your are looking for if you share some (dummy even) example of the structure of your project.
I'll try to break it down and address what I can anyway:
With regards to:
Usage-Tracker:
You can create a new client and bind to a scope. That way any use of the SentrySdk static class (which I assume your Logger.Log routes to) will pick up.
In other words, call SentrySdk.Init as you currently do, with the options that are shared across any application using your shared library, and after that create a client using the DSN of your Usage-Tracker project in sentry. Push a scope, bind the client and you can use SentrySdk with it.
There's an example in the GitHub repo of the SDK:
using (SentrySdk.PushScope())
{
SentrySdk.AddBreadcrumb(request.Path, "request-path");
// Change the SentryClient in case the request is to the admin part:
if (request.Path.StartsWith("/admin"))
{
// Within this scope, the _adminClient will be used instead of whatever
// client was defined before this point:
SentrySdk.BindClient(_adminClient);
}
SentrySdk.CaptureException(new Exception("Error at the admin section"));
// Else it uses the default client
_middleware?.Invoke(request);
} // Scope is disposed.
The SDK only has to be initialized once but you can always create a new client with new SentryClient, push a new scope (SentrySdk.PushScope()) and bind it to that new scope (SentrySdk.BindClient). Once you pop the scope the client is no longer accessdible via SentrySdk.CaptureException or any other method on the static class SentrySdk.
You can also use the client directly, without binding it to the scope at all.
using (var c = new SentryClient(new SentryOptions { Dsn = new Dsn("...") })) {
c.CaptureMessage("hello world!");
}
The using block is there to make sure the background thread flushes the event.
Central place to initialize the SDK:
There will be configuration which you want to have fixed in your shared framework/library but surely each application (composition root) will have its own setting. Release is auto-discovered.
From docs.sentry.io:
The SDK will firstly look at the entry assembly’s AssemblyInformationalVersionAttribute, which accepts a string as value and is often used to set a GIT commit hash.
If that returns null, it’ll look at the default AssemblyVersionAttribute which accepts the numeric version number.
If you patch your assemblies in your build server, the correct Release should be reported automatically. If not, you could define it per application by taking a delegate that passes the SentryOptions as argument.
Something like:
Framework code:
public class MyLogging
{
void Init(Action<SentryOptions> configuration)
{
var o = new SentryOptions();
// Add things that should run for all users of this library:
o.AddInAppExclude("SomePrefixTrueForAllApplications");
o.AddEventProcessor(new GeneralEventProessor());
// Give the application a chance to reconfigure anything it needs:
configuration?.Invoke(o);
}
}
App code:
void Main()
{
MyLogging.Init(o => o.Environment = "my env");
}
The scope is being disposed of so we are no longer getting Unhandled exceptions."
Not sure I understand what's going on here. Pushing and popping (disposing) scopes don't affect the ability of the SDK to capture unhandled exceptions. Could you please share a repro?
This logging method calls SentrySdk.Init() which I suspect is disposing the client in the executing assembly.:
Unless you create a client "by hand" with new SentryClient, there's only 1 client in the running process. Please note I said running process and not assembly. Instances are not held within an assembly. The assembly only contains the code that can be executed. If you call SentrySdk.CaptureException it will dispatch the call to the SentryClient bound to the current scope. If you didn't PushScope, there's always an implicit scope, the root scope. In this case it's all transparent enough you shouldn't care there's a scope in there. You also can't dispose of that scope since you never got a handle to do so (you didn't call PushScope so you didn't get what it returns to call Dispose on).
All the references in our individual applications are manually pointed to the dlls the folder where our shared library thingy installs itself.:
One thing to consider, depending on your environment is to distribute packages via NuGet. I'm unsure whether you expect to use these libraries in non .NET Framework applications (like .NET Core). But considering .NET Core 3.0 is bringing Windows Desktop framework support like WPF and WinForm, it's possible that eventually you will. If that's the case, consider targeting .NET Standard instead of .NET Framework for your code libraries.

What's the difference between Application and AppDomain.CurrentDomain

I am little confused about usage of Application class and AppDomain class.
For example Application.StartupPath is equal to AppDomain.CurrentDomain.BaseDirectory
I usually used Application class and recently discovered AppDomain - Can someone explain to me AppDomain class and its usage?
They have nothing in common, really.
Application is a class specific to Windows Forms, a .NET GUI technology. The Application.StartupPath is handled by the Kernel32 function GetModuleFileName. Through not passing a pointer to a module, the main module's path is returned - that is the exe file, basically.
AppDomain is a core .NET concept for domain isolation. Basically, it allows you to isolate (imperfectly of course) multiple applications in a single native process. Most applications only have a single AppDomain, but you can create as many as you like. The base path of an application domain is handled by Fusion, a .NET assembly loading technology. A very typical example would be ASP.NET applications hosted in IIS - each application has its own AppDomain, but they're all hosted in a single native process ("application pool"). Each logical application can be restarted without touching the others, and they don't have (simple) access to each other, but a process-tearing exception (e.g. StackOverflowException) will still kill the whole pool.
Another interesting class that's somewhat related is Environment. You can use Environment.CommandLine to get the process command line (which includes the path to the executable, including the name of the executable). This is basically a communication interface between the CLR and the underlying system - in this case, it takes care of saving the arguments for the application (which are passed by the OS to the Main function) and making them available at any time in the future.
Environment.CommandLine is somewhat clunky to parse (it's the raw command-line, basically - I assume it will have different conventions on Windows than on Linux, for example), but it's the only way you can always get to the executable. Again, Application.StartupPath is Winforms specific, and you can have more than one AppDomain - and possibly, the AppDomain might not even have a reasonable BaseDirectory.
The .NET Reflection APIs also give you a few ways. For example, Assembly.GetEntryAssembly() will give you the executable assembly - however, this only works for the main AppDomain - other domains will have their own entry assemblies (in fact, they'll usually just return null :)). You can get the path to an assembly through the Assembly.CodeBase property, but do note that this might not always be what you expect. You can also use Assembly.Location, or get the FullyQualifiedName of any of the assembly's modules (again, most assemblies only have a single module; and again, ASP.NET is one of the prime examples of when this isn't the case).

ReflectionOnlyLoad can it be garbage collected?

I want to "hot" load some pre-packaged assembli(es) into a separate AppDomain, the thing however is I do not know the name of the entry point class or even the assembly file. I need to find this entry point so I can run some initialization routine.
So what I intend to do is to run ReflectionOnlyLoad on all the files and find the one that follows a certain convention ie. annotated/implements a certain interface etc.
Question is, will I start leaking memory if I were to run ReflectionOnlyLoad from the main AppDomain over and over? If this can't be run from the main app domain, what are my options, because again I do not know where the entry point is.
Also any additional information about the subtleties in using ReflectionOnlyLoad is appreciated.
I recommend Mono.Cecil. It's a simple assembly you can use on .net (it doesn't require the Mono runtime). It offers an API to load assemblies as data, and works pretty well. I found the API easy to work with, and it suffered from none of the problems I experienced when using reflection-only-load.
You can also use CCI, which is an open source project by MS that offers an assembly reader.
See also: CCI vs. Mono.Cecil -- advantages and disadvantages
ReflectionOnlyLoad won't solve your problem, see docs
Why don't you execute the code for finding the entry point etc. in the new AppDomain?
Cannot reflect through the dlls. Even with reflection only load, the type sticks to the main AppDomain.
2 Solutions:
Put the entry point in an xml somewhere and parse that.
Use a
2 stage AppDomain, one for the reflector, and then another for the
actual object.
I picked (1) since it's the most sensible.
(2) I have to pass through 2 separate proxies in order to issue command to the actual remote object, that or I need to couple the interfaces much more closely than I like. Not to mention being a pain to code.

Marshalling .NET Object when class is in another namespace

I am having a problem with Marshalling an object across application domains in a .NET Windows Service.
I have created two app's that marshal an object across an app domain and run code through a proxy MarshalByRefObject
The first application was a simple proof of concept since I do not have a lot of experience with marshalling across application domains. It contains a single project where the MarshalByRefObject is defined in the same namespace as the rest of the project. This app works fine, using almost the same code as my other app. The main difference is that the class I am Marshalling in the second app is defined in another Namespace.
The other project is more complex, it is a Windows Service with multiple projects. The main Windows Service loads a library that that does the marshalling. The class type of the Marshal target type is defined in another library, so I use the fully qualified namespace/class name.
The problem I am facing is that when it gets to the last line of the code below it throws an exception:
Could not load CompanyName.ProductGroup.BusinessObjects.ProductName.MarshalByRefScriptCompiler from assembly ProductNameService where product name is the main Windows Service class.
The Code:
AppDomain compilerDomain = null;
AppDomainSetup compilerDomainSetup;
CompanyName.ProductGroup.BusinessObjects.ProductName.MarshalByRefScriptCompiler scriptCompiler;
...
// Setup a seperate AppDomain
compilerDomainSetup = new AppDomainSetup();
exeAssembly = Assembly.GetEntryAssembly().FullName;
compilerDomainSetup.ApplicationBase = System.Environment.CurrentDirectory;
compilerDomainSetup.DisallowBindingRedirects = false;
compilerDomainSetup.DisallowCodeDownload = true;
compilerDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
compilerDomain = AppDomain.CreateDomain("LiveLinkCSScriptDomain", null, compilerDomainSetup);
// Create an instance of the MarshalByRefScriptCompiler in the other AppDomain
scriptCompiler = (CompanyName.ProductGroup.BusinessObjects.ProductName.MarshalByRefScriptCompiler)compilerDomain.CreateInstanceAndUnwrap(exeAssembly, typeof(CompanyName.ProductGroup.BusinessObjects.ProductName.MarshalByRefScriptCompiler).FullName);
I have done research on this exception and almost everything I find says that it is a problem with versioning of the DLL, however my DLL's are not in the GAC, and there are no other versions installed. I am doing a clean build and installing the service with installutil.
I used the MSDN documentation as a guide for creating the code that does the marshalling
I am wondering if there is an issue with loading the MarshalByRefScriptCompiler because the type is in another library. I am able to create a MarshalByRefScriptCompiler in a simple winforms app, but I get the exception in my windows service.
Any tips or insight would be greatly appreciated!
I should be able to help you. I've spent many hours recently (How do I pass references as method parameters across AppDomains?) working on different cross appdomain marshaling problems. My first suggestion would be to try using CreateInstanceFromAndUnwrap instead of CreateInstanceAndUnwrap.
I'm also a little wary of this line:
compilerDomainSetup.ApplicationBase = System.Environment.CurrentDirectory;
How is your original AppDomain being created? Are you hosted in IIS, in which case your original AppDomain will be using ShadowCopy? Are all the dlls involved in a single folder?
EDIT:
To summarize, you can use CreateInstanceAndUnwrap if your compilerDomainSetup.ApplicationBase is set to the directory containing your dll and you pass in the correct first param (e.g. typeof(MarshalByRefScriptCompiler).Assembly.FullName).
Or, you can use CreateInstanceFromAndUnwrap and just pass in the location (e.g. typeof(MarshalByRefScriptCompiler).Assembly.Location) of the containing assembly as the first param.
As a starting point, you could try Process Monitor to determine where it's trying to load your missing type from. It's very possible that it's as simple as it looking in the wrong directory for your assembly.

Passing data to .NET C# WPF applications/DLLs

I have a .NET C# WPF application that I am trying to make into a single-instance application using a Mutex.
This .NET application is called by a C++-based DLL using CreateProcessAsUser() and is given parameters via environment variables.
Subsequent instances will also be created by the C++ DLL in the same way.
Subsequent instances would then need to pass their parameters to the first instance of the application before exiting.
The problem is what methods can be used in the .NET application so that the subsequent instances would be able to pass their data to the first instance of the .NET application? The simpler, the better.
I have researched some but I hope there are simpler ways.
Things I have researched:
Named Pipes
.NET Remoting
Windows Messaging (Sending WM_COPYDATA to the first instance window)
Since I am just trying to pass 4 strings to the first instance, I am trying to avoid the above mentioned methods because they are somewhat overkill for my problem.
The simplest I can think of is to export a function from the .NET application so that the subsequent instances of the .NET application can just call this function on the first instance of the .NET application and pass the data as the parameters of the function. However, is this possible in .NET? I've read that .NET EXE or DLLs could not export functions.
Thanks!
The simplest I can think of is to export a function from the .NET application and then the subsequent instances can just call this function and pass the parameters to it.
This is not how this works. You'll load the .NET assembly in the calling process, not magically cross the process boundary and talk to the child.
Just have the parent open the child with redirected pipes using the Process class, and have the child read from stdin using Console.Read*
thanks for the reply, Paul!
I've added more detail to my question above, though, coz I'm not sure if my scenario was understood correctly.
But regarding your answer, the parent of the .NET app will be a C++-based DLL and all it will do is call the .NET app and give it parameters. The C++-based DLL will also exit after this so I wouldn't want to add anymore behavior to it.
So, passing of data would then be done between the instances of the .NET applications only.
Since you are going from .NET to .NET, I'd recommend just doing a WCF call. You can use a named pipes transport between the two .NET instances to expose the "service" (which is what your first instance would expose).
Subsequent instances would do the single instance check, and if they detect an already running instance, they could make a WCF call to the service running in the first instance and pass the data that way.

Categories

Resources