Assist me with this C# pointer code - c#

I am posting a code.
using System;
using System.Runtime.InteropServices;
class TestPointer
{
public static void Main(string[] args)
{
if (args.Length == 0)
{
unsafe
{
int t = 8;
int* p = &t;
IntPtr addr = (IntPtr)p;
Console.WriteLine(addr.ToString("x"));
Console.WriteLine("before: " +(*p));
Console.ReadLine();
Console.WriteLine("after: " + (*p));
}
}
else
{
unsafe
{
string str = args[0];
GCHandle handle = GCHandle.Alloc(str, GCHandleType.Pinned);
IntPtr pointer = GCHandle.ToIntPtr(handle);
int* p = (int*)pointer;
int t = 5;
p = &t;
Console.WriteLine((*p));
}
}
}
}
i have run this code in two instances.
in instance1 I called as TestPointer.exe, the instance1 show memory location of 8 and than execuation stopped at Console.ReadLine() statement. On next step i run another instance (instance2) with TestPointer.exe 12f470(the memory address shown from instance1) so in this case i am changing value from 8 to 5 and after ReadLine from instance1
should show value 5 but it is still displaying 8. what is the reason?

The two processes have two different virtual address spaces. I would be absolutely horrified if one process could stomp on the values within another process without explicitly performing some sort of sharing (memory mapped files etc).
Was this an exercise in education, or is there something you're trying to achieve, and this was just an initial attempt? If it's the latter, please give us more details about what you're trying to do.

Well, for one thing, memory is isolated between instances. This wasn't true in the days of MS-DOS, but nowadays, it's the "prime directive" of every modern OS.
So you'll never be able to communicate data across instances in this way.
For another thing, the memory allocator does not guarantee that it will allocate memory in the same place once it's called -- far from it. My advice is to stay away from hardcoded addresses.
And for a bit of perspective here... It seems like you need to learn a lot of fundamentals about the OS, the CLR and memory management. To me, that means you should not be touching the "unsafe" construct. You're playing with fire. It's an advanced construct, primarily made for interoperability with older codebases. My advice is to stay away from it.

The cause is that you cannot access another process' memory so easily.
That's called 'Virtual memory' and it's the way modern OSes protect running processes' memory from being damaged.

Related

Clear stack variable memory

I have 8 uints which represent a security key like this:
uint firstParam = ...;
uint secondParam = ...;
uint thirdParam = ...;
uint etcParam = ...;
uint etcParam = ...;
They are allocated as local variables, inside of an UNSAFE method.
Those keys are very sensitive.
I was wondering do those locals on the stack get deleted when the method is over? Does the UNSAFE method have an affect on this? MSDN says that Unsafe code is automatically pinned in memory.
If they are not removed from memory, will assigning them all to 0 help at the end of the method, even though analyzers will say this has no effect?
So I tested zeroing out the variables. However, in x64 Release mode the zeroing is removed from the final product (checked using ILSpy)
Is there any way to stop this?
Here is the sample code (in x64 Release)
private static void Main(string[] args)
{
int num = new Random().Next(10, 100);
Console.WriteLine(num);
MethodThatDoesSomething(num);
num = 0; // This line is removed!
Console.ReadLine();
}
private static void MethodThatDoesSomething(int num)
{
Console.WriteLine(num);
}
The num = 0 statement is removed in x64 release.
I cannot use SecureString because I'm P/Invoking into a native method which takes the UInts as a paramter.
I'm P/Invoking into the unmanaged method AllocateAndInitializeSid, which takes 8 uints as parameters. What could I do in this scenerio?
I have tried adding
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
to the sample code (above Main method), however, the num = 0 is STILL removed!
EDIT: after some reasoning I've come to correct this answer.
DO NOT use SecureString, as #Servy and #Alejandro point out in the comments, it is not considered really secure anymore and will give a misguided sense of security, probably leading to futhering unconsidered exposures.
I have striked the passages I'm not comfortable with anymore and, in their place, would recommend as follows.
To assign firstParam use:
firstParam = value ^ OBFUSCATION_MASK;
To read firstParam use (again):
firstParam ^ OBFUSCATION_MASK;
The ^ (bitwise XOR) operator is the inverse of itself, so applying it twice returns the original value. By reducing the time the value exists without obfuscation (for the CPU time is actually the number of machine code cycles), its exposure is also reduced. When the value is stored for long-term (say, 2-3 microseconds) it should always be obfuscated. For example:
private static uint firstParam; // use static so that the compiler cannot remove apparently "useless" assignments
public void f()
{
// somehow acquire the value (network? encrypted file? user input?)
firstParam = externalSourceFunctionNotInMyCode() ^ OBFUSCATION_MASK; // obfuscate immediately
}
Then, several microseconds later:
public void g()
{
// use the value
externalUsageFunctionNotInMyCode(firstParam ^ OBFUSCATION_MASK);
}
The two external[Source|Usage]FunctionNotInMyCode() are entry and exit points of the value. The important thing is that as long as the value is stored in my code it is never in the plain, it's always obfuscated. What happens before and after my code is not under our control and we must live with it. At some point values must enter and/or exit. Otherwise what program would that be?
One last note is about the OBFUSCATION_MASK. I would randomize it for every start of the application, but ensure that the entropy is high enough, that means that the count of 0 and 1 is maybe not fifty/fifty, but near it. I think RNGCryptoServiceProvider will suffice. If not, it's always possible to count the bits or compute the entropy:
private static readonly uint OBFUSCATION_MASK = cryptographicallyStrongRandomizer();
At that point it's relatively difficult to identify the sensitive values in the binary soup and maybe even irrelevant if the data was paged out to disk.
As always, security must be balanced with cost and efficiency (in this case, also readability and maintainability).
ORIGINAL ANSWER:
Even with pinned unmanaged memory you cannot be sure if the physical memory is paged out to the disk by the OS.
In fact, in nations where Internet Bars are very common, clients may use your program on a publicly accessible machine. An attacker may try and do as follows:
compromise a machine by running a process that occasionally allocates all the RAM available;
wait for other clients to use that machine and run a program with sensitive data (such as username and password);
once the rogue program exhausts all RAM, the OS will page out the virtual memory pages to disk;
after several hours of usage by other clients the attacker comes back to the machine to copy unused sectors and slack space to an external device;
his hope is that pagefile.sys changed sectors several times (this occurs through sector rotation and such, which may not be avoided by the OS and can depend on hardware/firmware/drivers);
he brings the external device to his dungeon and slowly but patiently analyze the gathered data, which is mainly binary gibberish, but may have slews of ASCII characters.
By analyzing the data with all the time in the world and no pressure at all, he may find those sectors to which pagefile.sys has been written several "writes" before. There, the content of the RAM and thus heap/stack of programs can be inspected.
If a program stored sensitive data in a string, this procedure would expose it.
Now, you're using uint not string, but the same principles still apply. To be sure to not expose any sensitive data, even if paged out to disk, you can use secure versions of types, such as SecureString.
The usage of uint somewhat protects you from ASCII scanning, but to be really sure you should never store sensitive data in unsafe variables, which means you should somehow convert the uint into a string representation and store it exclusively in a SecureString.
Hope that helps someone implementing secure apps.
In .NET, you can never be sure that variables are actually cleared from memory.
Since the CLR manages the memory, it's free to move them around, liberally leaving old copies behind, including if you purposely overwrite them with zeroes o other random values. A memory analyzer or a debugger may still be able to get them if it has enough privileges.
So what can you do about it?
Just terminating the method leaves the data behind in the stack, and they'll be eventually overwritten by something else, without any certainity of when (or if) it'll happen.
Manually overwriting it will help, provided the compiler doesn't optimize out the "useless" assignment (see this thread for details). This will be more likely to success if the variables are short-lived (before the GC had the chance to move them around), but you still have NO guarrantes that there won't be other copies in other places.
The next best thing you can do is to terminate the whole process immediately, preferably after overwritting them too. This way the memory returns to the OS, and it'll clear it before giving it away to another process. You're still at the mercy of kernel-mode analyzers, though, but now you've raised the bar significantly.

C# - how does variable scope and disposal impact processing efficiency?

I was having a discussion with a colleague the other day about this hypothetical situation. Consider this pseudocode:
public void Main()
{
MyDto dto = Repository.GetDto();
foreach(var row in dto.Rows)
{
ProcessStrings(row);
}
}
public void ProcessStrings(DataRow row)
{
string string1 = GetStringFromDataRow(row, 1);
string string2 = GetStringFromDataRow(row, 2);
// do something with the strings
}
Then this functionally identical alternative:
public void Main()
{
string1 = null;
string2 = null,
MyDto dto = Repository.GetDto();
foreach(var row in dto.Rows)
{
ProcessStrings(row, string1, string2)
}
}
public void ProcessStrings(DataRow row, string string1, string string2)
{
string1 = GetStringFromDataRow(row, 1);
string2 = GetStringFromDataRow(row, 2);
// do something with the strings
}
How will these differ in processing when running the compiled code? Are we right in thinking the second version is marginally more efficient because the string variables will take up less memory and only be disposed once, whereas in the first version, they're disposed of on each pass of the loop?
Would it make any difference if the strings in the second version were passed by ref or as out parameters?
When you're dealing with "marginally more efficient" level of optimizations you risk not seeing the whole picture and end up being "marginally less efficient".
This answer here risks the same thing, but with that caveat, let's look at the hypothesis:
Storing a string into a variable creates a new instance of the string
No, not at all. A string is an object, what you're storing in the variable is a reference to that object. On 32-bit systems this reference is 4 bytes in size, on 64-bit it is 8. Nothing more, nothing less. Moving 4/8 bytes around is overhead that you're not really going to notice a lot.
So neither of the two examples, with the very little information we have about the makings of the methods being called, creates more or less strings than the other so on this count they're equivalent.
So what is different?
Well in one example you're storing the two string references into local variables. This is most likely going to be cpu registers. Could be memory on the stack. Hard to say, depends on the rest of the code. Does it matter? Highly unlikely.
In the other example you're passing in two parameters as null and then reusing those parameters locally. These parameters can be passed as cpu registers or stack memory. Same as the other. Did it matter? Not at all.
So most likely there is going to be absolutely no difference at all.
Note one thing, you're mentioning "disposal". This term is reserved for the usage of objects implementing IDisposable and then the act of disposing of these by calling IDisposable.Dispose on those objects. Strings are not such objects, this is not relevant to this question.
If, instead, by disposal you mean "garbage collection", then since I already established that neither of the two examples creates more or less objects than the others due to the differences you asked about, this is also irrelevant.
This is not important, however. It isn't important what you or I or your colleague thinks is going to have an effect. Knowing is quite different, which leads me to...
The real tip I can give about optimization:
Measure
Measure
Measure
Understand
Verify that you understand it correctly
Change, if possible
You measure, use a profiler to find the real bottlenecks and real time spenders in your code, then understand why those are bottlenecks, then ensure your understanding is correct, then you can see if you can change it.
In your code I will venture a guess that if you were to profile your program you would find that those two examples will have absolutely no effect whatsoever on the running time. If they do have effect it is going to be on order of nanoseconds. Most likely, the very act of looking at the profiler results will give you one or more "huh, that's odd" realizations about your program, and you'll find bottlenecks that are far bigger fish than the variables in play here.
In both of your alternatives, GetStringFromDataRow creates new string every time. Whether you store a reference to this string in a local variable or in argument parameter variable (which is essentially not much different from local variable in your case) does not matter. Imagine you even not assigned result of GetStringFromDataRow to any variable - instance of string is still created and stored somewhere in memory until garbage collected. If you would pass your strings by reference - it won't make much difference. You will be able to reuse memory location to store reference to created string (you can think of it as the memory address of string instance), but not memory location for string contents.

How to find out memory consumed by classes, objects, variables, etc [duplicate]

This question already has answers here:
How to get object size in memory? [duplicate]
(6 answers)
Closed 7 years ago.
I am trying to play with memory profiling (for the first time, so please forgive my ignorance), just to see how much memory is consumed by classes, objects, variables, methods etc. I wrote this sample c# console program called MemPlay.exe:
using System;
using System.Text;
namespace MemPlay
{
class Program
{
static void Main(string[] args)
{
SomeClass myObject = new SomeClass();
StringNineMethod();
}
private static void StringNineMethod()
{
string someString0 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string someString1 = string.Empty;
string someString2 = string.Empty;
for (int i = 0; i < 9999; i++) {
someString1 += "9";
someString2 += someString1;
}
}
}
class SomeClass
{
}
}
Once the program ran, I want to find out:
How much memory was consumed by
MemPlay.exe
Program class
SomeClass
Main method
myObject
StringNineMethod
someString0
someString1
someString2
and how much processor was used by:
MemPlay.exe
Main method
StringNineMethod
I tried using VisualStudio's 'Performance Diagnostics' tool, but all I can see is how much memory was used by the whole function (i.e. Main method, StringNineMethod, and the String.Concat method).
Is there any way/tool which can help me see all the details of how much memory each variable, object, class, method consumed? Thanks in advance.
EDIT: No my question is not duplicate of the question suggested, as that question is trying to get object sizes at runtime, I am asking how can I get this information after the program has ended. Just what Visual Studio's Performance Diagnostics tool does, it gives this information after the program has ended execution.
You can use System.Diagnostics namespace classes to get different kind of measurements and statistics. To get total memory allocated for the process use WorkingSet property (more details on MSDN):
Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
long processAllocatedMemory = currentProcess.WorkingSet64;
So that's process.
To get specific object allocation you probably can use GC to check initial memory, then allocate an object and finally to check memory again:
// Check initial memory
var memoryStart = System.GC.GetTotalMemory(true);
// Allocate an object.
var myClass = new SomeClass;
// Check memory after allocation
var memoryEnd = System.GC.GetTotalMemory(true);
To check memory consumption on specific process after specific operation you probably can use the same trick as with the GC only on the current process (like in the first example).
To check executables and programs use Visual Studio profiler. In VS2013 Community Edition go to ANALYZE -> Performance and Diagnostics menu (or hit Alt+F2). It allows you to analyze standard project, an exe, an ASP.NET website, and Windows Phone App:
There, you select Performance Wizard, click Start, and in the next step you have a choice of metrics you would like to run. One of which is memory consumption:
I use this one :
RedGate ANTS
I've also used this one in the past :
SciTech Memory Profiler
They both have free trials and are worth a look at. I've been impressed enough at various times to buy versions of both.
(I don't work for either company - just recommending what has worked for me - there are other tools out there too such as the JetBrains Memory Profiler, but I've not tried that personally and can't offer an opinion).

Memory leak in unmanaged code?

I have traced a leak pretty far but I can't seem to understand (fix) it by myself. I used ANTS memory profiler first to make sure my code actually is stacking memory. It starts from using 25 MB but within an hour or so it is using over 100 MB. A friend of mine for whom I'm coding this for has actually been using this faulty program and he got it to spend his whole 18 GB of ram and got a out of memory exception.
The leaking part is not vital for the program, but it just is pretty much useless without the RefreshSessions() method.
I have been extending the project Vista Core Audio API Master Volume Control from Code Project.
This is the part which seems to leak. Tested by not using it and then it doesn't leak.
Updated:
public void RefreshSessions()
{
Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
_Sessions.Refresh(_SessionEnum);
}
(Removed the class code from here)
I have not been coding too much so I may have missed something, but if more details are needed you can actually download the source or I can just answer to my best ability.
(Removed unnecessary code here)
The leak was tested with this simple console app:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MMDeviceEnumerator DevEnum = new MMDeviceEnumerator();
MMDevice device = DevEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
Console.ReadKey();
int i = 0;
while (i < 10000)
{
device.AudioSessionManager.RefreshSessions();
i++;
}
Console.ReadKey();
}
}
}
Update 2
I think I got it fixed. Have to run some longer tests, but at least it seems like the memory usage has stabilized. The idea came from dialer who found a fix for the leak in c++.
public void RefreshSessions()
{
_Sessions.Release(); //added this
IAudioSessionEnumerator _SessionEnum;
Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
_Sessions.Refresh(_SessionEnum);
}
This is the part in SessionCollection:
public void Release()
{
Marshal.ReleaseComObject(_AudioSessionEnumerator);
}
This is not exactly the code dialer suggested (which I ended up using anyways), but still.
And as he said as well this might not be the best way to achieve this but I will go with it since it does not seem to have any adverse effects on my app.
ANOTHER EDIT
public void RefreshSessions()
{
if (_SessionEnum != null)
{
Marshal.ReleaseComObject(_SessionEnum);
}
Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
}
Above code releases the SessionEnum explicitly and also fixed the leak in C#. This should probably be taken care of in a better way though.
EDIT:
The following C++ program is equivalent to what you did in the loop test program. The Release call at the end of the for loop fixes the leak. I need to go for today, maybe you can play around a bit and try to fix it yourself. Or maybe someone else can find out and explain why the CLR garbage collector does not call the Release automatically at some point in the C# program above.
#include <stdio.h>
#include <tchar.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>
#define SAFE_RELEASE(p) { if ( (p) ) { (p)->Release(); (p) = 0; } }
#define CHECK_HR(hr) if (FAILED(hr)) { goto done; }
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioSessionManager2 = __uuidof(IAudioSessionManager2);
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr;
CoInitialize(0);
IMMDeviceEnumerator *deviceEnum = 0;
CHECK_HR(hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&deviceEnum));;
IMMDevice *endpoint = 0;
CHECK_HR(deviceEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &endpoint));
getchar();
// lazy initialization as found in MMDevice.AudioSessionManager..get
IAudioSessionManager2 *m = 0;
CHECK_HR(endpoint->Activate(IID_IAudioSessionManager2, CLSCTX_ALL, 0, (void **)&m));
for (int i = 0; i < 100000; i++)
{
IAudioSessionEnumerator *sessEnum = 0;
m->GetSessionEnumerator(&sessEnum);
sessEnum->Release(); // leak
}
getchar();
printf("success");
return 0;
done:
printf("failure");
return 1;
}
OLD
My guess:
_AudioSessionManager.GetSessionEnumerator(out _SessionEnum) yields an enumerator. When you call the constructor SessionCollection(_SessionEnum), then _SessionEnum is being enumerated over. Each enumeration step retrieves an actual unmanaged object.
If it's a value type, then it would actually be copied into session collection (remember that the List(IEnumerable e) constructor copies each element). The copy would then be garbage collected, but the original object was allocated from unmanaged code and procudes a leak. If this is the case, you should free the memory immediately after calling the Collection constructor using some unmanage memory free function.
If it's a reference type, it wouldn't be freed either because the actual object in the memory isn't garbage collected, since it was allocated from within unmanaged code. If this is the case, you need to free the memory of the objects with unmanaged library functions when you no longer need them.
If you have unmanaged code, when is the _Sessions memory released? If you simply reassign the private field, then the memory is never released.
Here's an example:
http://social.msdn.microsoft.com/forums/en-US/clr/thread/b2162d42-0d7a-4513-b02c-afd6cdd854bd
You need to use the dll's method for freeing up the memory (delete[] in C++)
.NET has always had the ability to easily leak memory - or rather, its doen to un-collected garbage that never gets cleaned up as the GC thinks they're in use. The most famous incident was the DARPA challenge team who believed the hype and thought the leak bug was in their C driver code, poor people.
Since those days, there have been quite a few memory leak profilers appear. I think the most famous one in Redgate's ANTS, but there are loads of others. run your app, see which objects out live their welcome, see which objects have a reference to them, put some code in unreferencing them at the right places (eg a few more Dispose and/or using statements).

.NET EventWaitHandle slow

I'm using waveOutWrite with a callback function, and under native code everything is fast. Under .NET it is much slower, to the point I think I'm doing something very wrong, 5 or 10 times slower sometimes.
I can post both sets of code, but seems like too much, so I'll just post the C code that is fast and point out the minor variances in the .NET code.
HANDLE WaveEvent;
const int TestCount = 100;
HWAVEOUT hWaveOut[1]; // don't ask why this is an array, just test code
WAVEHDR woh[1][20];
void CALLBACK OnWaveOut(HWAVEOUT,UINT uMsg,DWORD,DWORD,DWORD)
{
if(uMsg != WOM_DONE)
return;
assert(SetEvent(WaveEvent)); // .NET code uses EventWaitHandle.Set()
}
void test(void)
{
WaveEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
assert(WaveEvent);
WAVEFORMATEX wf;
memset(&wf,0,sizeof(wf));
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = 1;
wf.nSamplesPerSec = 8000;
wf.wBitsPerSample = 16;
wf.nBlockAlign = WORD(wf.nChannels*(wf.wBitsPerSample/8));
wf.nAvgBytesPerSec = (wf.wBitsPerSample/8)*wf.nSamplesPerSec;
assert(waveOutOpen(&hWaveOut[0],WAVE_MAPPER,&wf,(DWORD)OnWaveOut,0,CALLBACK_FUNCTION) == MMSYSERR_NOERROR);
for(int x=0;x<2;x++)
{
memset(&woh[0][x],0,sizeof(woh[0][x]));
woh[0][x].dwBufferLength = PCM_BUF_LEN;
woh[0][x].lpData = (char*) malloc(woh[0][x].dwBufferLength);
assert(waveOutPrepareHeader(hWaveOut[0],&woh[0][x],sizeof(woh[0][x])) == MMSYSERR_NOERROR);
assert(waveOutWrite(hWaveOut[0],&woh[0][x],sizeof(woh[0][x])) == MMSYSERR_NOERROR);
}
int bufferIndex = 0;
DWORD times[TestCount];
for(int x=0;x<TestCount;x++)
{
DWORD t = timeGetTime();
assert(WaitForSingleObject(WaveEvent,INFINITE) == WAIT_OBJECT_0); // .NET code uses EventWaitHandle.WaitOne()
assert(woh[0][bufferIndex].dwFlags & WHDR_DONE);
assert(waveOutWrite(hWaveOut[0],&woh[0][bufferIndex],sizeof(woh[0][bufferIndex])) == MMSYSERR_NOERROR);
bufferIndex = bufferIndex == 0 ? 1 : 0;
times[x] = timeGetTime() - t;
}
}
The times[] array for the C code always has values around 80, which is the PCM buffer length I am using. The .NET code also shows similar values sometimes, however, it sometimes shows values as high as 1000, and more often values in the 300 to 500 range.
Doing the part that is in the bottom loop inside the OnWaveOut callback instead of using events, makes it fast all the time, with .NET or native code. So it appears the issue is with the wait events in .NET only, and mostly only when "other stuff" is happening on the test PC -- but not a lot of stuff, can be as simple as moving a window around, or opening a folder in my computer.
Maybe .NET events are just really bad about context switching, or .NET apps/threads in general? In the app I'm using to test my .NET code, the code just runs in the constructor of a form (easy place to add test code), not on a thread-pool thread or anything.
I also tried using the version of waveOutOpen that takes an event instead of a function callback. This is also slow in .NET but not in C, so again, it points to an issue with events and/or context switching.
I'm trying to keep my code simple and setting an event to do the work outside the callback is the best way I can do this with my overall design. Actually just using the event driven waveOut is even better, but I tried this other method because straight callbacks are fast, and I didn't expect normal event wait handles to be so slow.
Maybe not 100% related but I faced somehow the same issue: calling EventWaitHandle.Set for X times is fine, but then, after a threshold that I can't mention, each call of this method takes 1 complete second!
Is appears that some .net way to synchronize thread are much slower than the ones you use in C++.
The all mighty #jonskeet once made a post on his web site (https://jonskeet.uk/csharp/threads/waithandles.html) where he also refers the very complex concept of .net synchronization domains explained here: https://www.drdobbs.com/windows/synchronization-domains/184405771
He mentions that .net and the OS must communicate in a very very very time precise way with object that must be converted from one environment to another. All this is very time consuming.
I summarized a lot here, not to take credit for the answer but there is an explanation. There are some recommendations here (https://learn.microsoft.com/en-us/dotnet/standard/threading/overview-of-synchronization-primitives) about some ways to choose how to synchronize depending on the context, and the performance aspect is mentioned a little bit.

Categories