0

In a process with many async threads, I set each thread to a specific name and monitor it regularly to see its status. Some threads seem to regularly lose their name value though. I can't find out why this happens.

public static void ChangeThreadName(this Thread current, string newName)
{
    if (string.IsNullOrEmpty(current.Name))
    {
        try
        {
            current.Name = newName;
        }
        catch (InvalidOperationException ex)
        {
            Log.Error(ex, $"Error when trying to change Threadname from [{current.Name}] to [{newName}]");
        }
    }
    else
    {
        Log.Warning($"Trying to change Threadname from [{current.Name}] to [{newName}]");
    }
}

Inside a while loop of an async task:

if (Thread.CurrentThread.ManagedThreadId != originalID)
{
    Log.Verbose($"Thread change detected at ThreadHandler{QueueID}-{threadNumberString}, oldID = {originalID} - newID = {Thread.CurrentThread.ManagedThreadId}");
    nr = await ThreadManager.ReRegisterProc(nr, threadName, false);
    originalID = Thread.CurrentThread.ManagedThreadId;
}
if (Thread.CurrentThread.Name != threadName)
{
    Log.Verbose("ThreadName changed to {status} -> reverting to {args}", Thread.CurrentThread.Name, threadName);
    Thread.CurrentThread.ChangeThreadName(threadName);
}

Renaming and reregistering the task works, but I'd prefer it not being necessary.

I tried adding the threads to a static list and looping it to see it's status, this doesn't work without reregistering as shown above.


EDIT:

It is now clear that monitoring threads might not be the way to go. However i still need to know which of all async threads are actually running and which are waiting. It regards a large variety of different threads which are created in classes that operate independent from eachother.

I currently use this code:

public partial class ThreadManagement
{
    private SemaphoreSlim KeySemaphore { get; } = new(1, 1);

    public async Task<List<Thread>> GetThreads()
    {
        await Task.CompletedTask;
        return TheseProcThreads.Values.ToList();
    }

    public async Task<Thread?> GetSpecificThread(long key)
    {
        if (TheseProcThreads.TryGetValue(key, out Thread FoundThread))
        {
            await Task.CompletedTask;
            return FoundThread;
        }
        else
        {
            await Task.CompletedTask;
            return null;
        }
    }

    public async Task<long> TryAddProc(long key, Thread value)
    {
        if (!TheseProcThreads.TryAdd(key, value))
        {
            key = await TryAddProc(await GetNewKey(), value);
        }
        return key;
    }
    public async Task<long> TryAddProc(long key, Thread value, string newName, ThreadPriority prio, bool background = false)
    {
        value.ChangeThreadName(newName);
        value.Priority = prio;
        value.IsBackground = background;
        return await TryAddProc(key, value);
    }
    public async Task<long> TryAddProc(Thread value)
    {
        return await TryAddProc(await GetNewKey(), value);
    }
    public async Task<bool> TryRemoveProc(long key)
    {
        if (!TheseProcThreads.TryRemove(key, out _))
        {
            Log.Error("{object} {actie} {args} {status}.", "Thread", "verwijderen", "uit dictionary","mislukt");
            await Task.CompletedTask;
            return false;
        }
        await Task.CompletedTask;
        return true;
    }
    public async Task<long> ReRegisterProc(long nr, string methodName, bool background)
    {
        Thread.CurrentThread.ChangeThreadName(methodName);
        try
        {
            Thread.CurrentThread.IsBackground = background;
        }
        catch (Exception Ex)
        {
            Log.Error(Ex, "{actie} {object} {status}", "Instellen", "thread background", "mislukt");
        }
        await TryRemoveProc(nr);
        return await TryAddProc(Thread.CurrentThread);
    }
    public async Task<long> GetNewKey()
    {
        await KeySemaphore.WaitAsync();
        await StaticKeySemaphore.WaitAsync();
        long tmpKey = TheseProcThreads.Count + 2;
        while (true)
        {
            if (ReservedKeys.Contains(tmpKey))
            {
                tmpKey++;
            }
            else
            {
                break;
            }
        }
        ReservedKeys.Add(tmpKey);
        KeySemaphore.Release();
        StaticKeySemaphore.Release();
        return tmpKey;
    }
}

public partial class ThreadManagement
{
    private static SemaphoreSlim StaticKeySemaphore { get; } = new(1, 1);
    private static ConcurrentDictionary<long, Thread> TheseProcThreads { get; } = new();
    private static List<long> ReservedKeys { get; } = new();
}
Randolf
  • 29
  • 4

2 Answers2

4

In async work, the thread is none of your business. You cannot and should not do anything that depends on specific threads, including (but not limited to):

  • Monitor usage (or other thread-bound locking primitives) that span an await
  • [ThreadStatic]
  • thread names / ids
  • thread local storage

Async and threads are entirely different concepts. There is no such thing as an "async thread". There is an async execution that may find itself on one of any number of different threads at different points (potentially switching at any await)

If you really need some ambient state, consider AsyncLocal<T>, but honestly; avoiding ambient state is preferable

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • If so, than how would it be possible to loop through a variety of threads to see which ones are waiting and which ones are actively running? – Randolf May 11 '23 at 09:28
  • @Randolf you're thinking in terms of threads; *stop doing that* - instead perhaps ask which tasks are still incomplete – Marc Gravell May 11 '23 at 20:04
1

No thread loses its Name property. It may appear to you that this is happening, but it is not.

What you think of as a thread is not a single thread, it is various different threads that get created and destroyed all the time behind the scenes.

That's because async makes use of threads from a ThreadPool.

This means that new threads will be created if at some moment you have many async tasks running in parallel, and old threads may be destroyed if at some moment you happen to have not so many, if any, tasks running in parallel, and all this is completely out of your control. (As it should, arguably.) Thus, newly created threads will appear to have no name, and threads that die will take their names to the grave with them.

I would recommend that you assign names to your tasks, not to the threads.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • A task however has some overhead, not all threads are created as tasks, some parts of the sollution are simply single long running threads. Also a task does not show me the actual status (running/waiting), only wether it is started and completed, which is insufficient for my purposes. – Randolf May 11 '23 at 09:30
  • 2
    True. So then maybe what we have in our hands is an [X/Y problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Perhaps you should describe your status-monitoring needs and ask for a solution, instead of describing the solution that you chose (naming threads) and asking why it is not working. – Mike Nakis May 11 '23 at 09:35