InternetException
About coding and whatnot
Notes on .NET, C#, self-hosted LLMs, Android and whatever else I break on the way. Written since 2012, migrated from WordPress to a few static files. Who is writing this?
Locking with integer
Locking in .NET
lockstatement blocks execution of a code block for single thread that hold lock- the same thread can acquire and release lock again
- any other thread is blocked from the statement
- lock on dedicated instance only
- using the same instance on multiple
lockstatements may lead to deadlock - avoid using as lock
- Hold a lock for as short time as possible to reduce lock contention.
Below code block is translated by compiler
Thread vs task in . NET
What is SynchronizationContext in .NET
- multi threaded programs usually uses one of the threads as means of synchronization
- there are message loops to pass unit of works (like Windows message queue)
- message queues require messages formats and convention for handling it
- single thread to handle ASP.NET request is waste of the resources (before .NET 2)
- thread pool was better but inconvenient to work with
- instead SynchronizationContext queues work for given context not thread
- thread have current context
- context does not have to be unique but can be shared between multiple threads
- thread can change context but this is rare
- SynchronizationContext keeps count of all asynchronous operations
- Windows Forms uses WindowsFormsSynchronizationContext
- WPF and Silverlight uses DispatcherSynchronizationContext
- Console app and WindowsServices and ThreadPool threads uses default SynchronizationContext
- ASP.NET uses AspNetSynchronizationContext
Further read:
Does async/await creates new thread?
In short: no.
Longer explanation:
- task that want to do I/O write call to driver write
- driver operations are performed asynchronously
- write operations on devices is done without CPU involved
- finish of writing is done via interrupt
- interrupt, schedules
Deferred Procedure Call DPCschedulesAsynchronous Procedure CallAPCnotifies thread that I/O write is done- task that was doing I/O is scheduled with continuation of its method after I/O is done.
- some threads might need to be borrowed for handling APC and such but no thread is spawn to handle I/O
I recommend reading this post: https://blog.stephencleary.com/2013/11/there-is-no-thread.html . This will explain the topic much better than I can.
Avoid deadlock with asynchronous methods
Asynchronous programming in .NET
async/await in C#
async/await syntax should be used for I/O bound asynchronous operations only.
public async Task<int> GetUrlContentLengthAsync()
{
var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://docs.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
Compiler transforms code into a state machine that to track yielding execution for await and resuming execution when a background job is finished.
Things to keep in mind:
asyncmethod needs await- add
Asyncsuffix to asynchronous methods async voidfor ‘Fire and Forget’ work i.e. event Handlers- be aware of mixing LINQ with
asynclambdas - wait for tasks in a non-blocking manner (do not use
Task.Resulti.e.) - for tight loops use
ValueTasks - Consider using
ConfigureAwait(false)for performance and for avoiding deadlocks
Further read:
CPU bound vs I/O bound
There are two types of asynchronous operations in .NET.
- I/O bound
- CPU bound
For I/O bound async/await syntax should be used. TPL should not be used for this. async methods does not spawns new thread to hand le I/O work asynchronously.
For CPU bound operations TPL should be used (Task.Run). This spawns new thread. Performance testing should be done to check if overhead of new thread and context switching is not greater than actual work to be done.
SupressFinalize in IDisposable interface
- informs GC that object does not need to be added to finalizer queue
- finalizer execution is not deterministic and objects can live for long time in queue - this is optimization technique
- to make sure that object disposes of any unamanaged resources even if
Disposewill be not called we need to implement finalizer
Basically finalizer execution time is not deterministic. We cannot predict when this will be called and till that time - object is existing and still taking memory space. Since we are controlling creation and disposal of those resources we can say to GC: ‘Do not worry about that! It already had been taken care of!’. So this way object will be destroyed sooner, leaving this space to be used by something else possibly.
IDisposable interface pattern
Example implementation of IDisposable
public class MyResource : IDisposable
{
public MyResource(IntPtr handle)
{
this.handle = handle;
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
component.Dispose();
}
CloseHandle(handle);
handle = IntPtr.Zero;
disposed = true;
}
}
~MyResource()
{
Dispose(disposing: false);
}
}
MyResource object have pointer which is unmanaged. This is why this class should implement IDisposable interface. Implementation should be correct according to recommended pattern (see below for documentation).
There a re few points to highlight here: