2

Is there a way to check if any "user" threads are running. I'd like to put this in the [TestCleanup] method, failing the test if a test hasn't joined all the threads it started.

I figured just listing all the currently running threads wouldn't help, as there's probably for example, garbage collection threads that start at indeterminate times. So I basically want the threads that aren't just associated with the runtime.

Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 1
    And you're definitely using threads and not tasks, right? – ProgrammingLlama Oct 28 '19 at 00:50
  • In the current codebase yes – Clinton Oct 28 '19 at 00:51
  • This test seems like its pointing to bigger problems, this shouldn't really need to be tested for, and if you do, it should probably be covered in another test – TheGeneral Oct 28 '19 at 00:55
  • I don't know what you mean, I want to cover this in all tests. It may or may not be a test code that starts the threads, but in either case they should be cleaned up once the test is complete. If they're not this seems like something we should be aware of hence the test should fail. – Clinton Oct 28 '19 at 01:02
  • It would be awesome if you could share a [mcve]. – mjwills Oct 28 '19 at 01:17
  • @mjwills I don't know what you mean. An example of a test that should fail is `Thread t = new Thread(() => { Thread.Sleep(10000000); }); t.Start();` but obviously the issues I'm trying to detect with a post test check are more complex. – Clinton Oct 28 '19 at 01:20
  • @mjwills: I'm not asking how to fix the tests with extra threads, I'm asking how to detect the extra threads. Once they're detected they can be fixed relatively easily just by stepping through with a debugger. – Clinton Oct 28 '19 at 01:22
  • Can you talk us through how this question differs from https://stackoverflow.com/questions/2773479/how-to-check-if-thread-finished-execution ? – mjwills Oct 28 '19 at 01:24
  • In the `[TestCleanup]` method I don't have access to the actual `Thread` objects. They have potentially been created anywhere and started. If they weren't joined and for whatever reason are still running they'll just be dangling at the end of the test, potentially affecting future tests (e.g. they might be locking files). So when a test doesn't clean after itself (or the code it calls doesn't clean up after itself) I want to detect this and fail the test. – Clinton Oct 28 '19 at 01:27
  • @Clinton Not sure if this could be of any help but, maybe have a look at winapi's `Thread32First`/`Thread32Next` & `OpenThread`, this will allow you to go through all the threads currently running (`First/Next`), find the ones belonging to your process, and then open them to do stuff to them. The issue is that you wont be able to step through them using a debugger, at least not in the VS way, and you wont be able to determine if it's a runtime thread or not, unless you figure out what the thread's start address is and what said address corresponds to. – Vincent Bree Oct 28 '19 at 01:38
  • @VincentBree thanks. I don't really need to do anything with the threads as I just need to fail the test (I'll then rerun the test with a debugger to debug the issue) but I don't want tests failing unnecessarily because of a new runtime thread that just happens to be created between the start and the end of the test. – Clinton Oct 28 '19 at 01:41
  • I see how this would be more relevant in C++, however tasks in C# are not threads and with async and await this becomes even less useful. . Checking for threads (or even tasks) in a wholesale sense seems just a little contrived. If you need to check whether a task is finished, that should be the the job of the method who created them. Which is what i was eluding to first (Said in the nicest possible way). Reading your last comment, i think i see better what you are trying to do, Though shouldn't your tests be stateless anyway? – TheGeneral Oct 28 '19 at 01:50
  • @TheGeneral I agree it should be the job of the method that created them. That's the point of the post test check I'm asking how to write: to ensure that the methods that create threads are doing what they should be doing, that is, cleaning up after themselves. – Clinton Oct 28 '19 at 01:52

2 Answers2

1

There are some really valid points in the comments section of the question but you could

  1. Try and see if thread counting works for you without worrying about
  2. Try TryStartNoGCRegion:
    GC.TryStartNoGCRegion(1024*1204*10);
    var count = System.Diagnostics.Process.GetCurrentProcess().Threads.Count;
    GC.EndNoGCRegion();
    

Here's a trivial example.

public static void Main()
{
    int GetThreadCount() {
        GC.TryStartNoGCRegion(1024*1204*10);
        var count = System.Diagnostics.Process.GetCurrentProcess().Threads.Count;
        GC.EndNoGCRegion();
        return count;
    }


    var count1 = GetThreadCount();
    Console.WriteLine($"Headcount at (in?) the beginning: {count1}");

    var t1 = new Thread(() => {       
            Thread.Sleep(1000);
        });
    t1.Start();

    var count2 = GetThreadCount();
    Console.WriteLine($"Headcount later: {count2}");

    if (count2 != count1 ) {
        Console.WriteLine("Oh no! Threads running!");
    }

    t1.Join();

    var count3 = GetThreadCount();
    Console.WriteLine($"Headcount even later: {count3}");

    if (count3 != count1 ) {
        Console.WriteLine("Oh no! Threads running!");
    } else {
            Console.WriteLine("Phew. Everybody Joined the party.");
    }

    Console.ReadLine();
}

Output

// .NETCoreApp,Version=v3.0
Headcount at (in?) the beginning: 10
Headcount later: 11
Oh no! Threads running!
The thread 9620 has exited with code 0 (0x0).
Headcount even later: 10
Phew. Everybody Joined the party.
tymtam
  • 31,798
  • 8
  • 86
  • 126
1

I would recommend taking a look at the Microsoft.Diagnostics.Runtime library (Nuget). Specifically the CLR capabilities. You can get a list of all threads with stacktraces with the following code

using (DataTarget target = DataTarget.AttachToProcess(Process.GetCurrentProcess().Id, 5000, AttachFlag.Passive))
{
    ClrRuntime runtime = target.ClrVersions.First().CreateRuntime();
    return new runtime.Threads;
}
Patrick
  • 677
  • 5
  • 14