Search code examples
c#multithreadingatomicappdomain

How to get the current thread id without AppDomain.GetCurrentThreadId(), so that it actually works?


Since AppDomin.GetCurrentThreadId() is obsolete

"AppDomain.GetCurrentThreadId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread. http://go.microsoft.com/fwlink/?linkid=14202"

I tried not to use it. However, the explaination that 'Thread.CurrentThread.ManagedThreadId' would work is worthless, because it does not provide a Win32 thread id, which I need for Win32 calls. So I implemented it myself as follows.

public sealed class AppDomainExtender
{
    public static int GetCurrentThreadId_New()
    {
        return Process.GetCurrentProcess().Threads.OfType<ProcessThread>().SingleOrDefault(x => x.ThreadState == System.Diagnostics.ThreadState.Running).Id;
    }
}

Now there are two problems.

  • If it would work, wouldn't it do exactly the same as AppDomain.GetCurrentThreadId?
  • It doesn't work for the reason that the running thread may change while looping through the SingleOrDefault loop. This behaviour is weird, but it just got me a InvalidOperationException ("Sequence contains more than one element"). Appearently it happens for example when using multiple desktops (I assume this is only possible by copying the thread which is executing the loop to another id).

Questions:

  1. Is AppDomain.GetCurrentThreadId falsely declared as obsolete?
  2. How could one make the statement "Process.GetCurrentProcess().Threads.OfType().SingleOrDefault(x => x.ThreadState == System.Diagnostics.ThreadState.Running).Id" atomic, or is it even possible?

Solution

  • The deprecation of AppDomain.GetCurrentThreadId() is an inconvenient truth, certainly not a "false" declaration. Starting at .NET 2.0, the association between a .NET Thread and an operating system thread was broken. A custom CLR host is free to implement threading the way it sees fit by implementing the IClrTask interface. Or in other words, you have no guarantee that two different .NET threads use different operating system threads with distinct thread IDs.

    This was in general a popular thing to do in the 1990s and early 2000s, going by names like "light-weight threads", co-routines, fibers, "green threads", etc. The idea was to avoid thread context switches by the operating system and context-switching the CPU registers in software instead. SQL Server was a primary beneficiary of this feature, it has built-in support for "fiber mode".

    While the SQL Server team got the feature, they ultimately decided to not ship it. They abandoned the project when they could not get it stable enough. A problem that probably inspired this article, published in the same year their project was due. This was not tried again. Not in the least because the feature was obsoleted by the multi-core revolution. The cost of a context switch in modern cores is dominated by the state of the CPU caches, it is impossible to compete with each core having its own L1 and L2 cache.

    I am not aware of any CLR host that actually implements IClrTask, the failure of the SQL Server team was certainly a big red flag. That however doesn't exclude the possibility, the kind of scenario you might want to fret about is running your code in the context of a large unmanaged application that supports scripting in a managed language. A CAD program would be a typical example of that.

    You'd be dead in the water in the very unlikely case your code actually would run in this context, so set your laser to stun and forge ahead. Beyond having to put up with the obsolescence warning, the most sanest workaround is to pinvoke GetCurrentThreadId(). It is very fast.