检查线程是否返回到线程池

6

如何使用VS C# 2015调试器检查线程是否返回到线程池?

在我的情况下,问题在于无法通过逐行调试检测到。

async Task foo()
{
    int y = 0;
    await Task.Delay(5);
    // (1) thread 2000 returns to thread pool here...
    while (y<5) y++;
}

async Task testAsync()
{
    Task task = foo();
    // (2) ... and here thread 2000 is back from the thread pool, to run the code below. I want
    // to confirm that it was in the thread pool in the meantime, using debugger.
    int i = 0;
    while (i < 100)
    {
        Console.WriteLine("Async 1 before: " + i++);

    }
    await task;
}

在运行在线程2000上的testAsync的第一行中,调用了foo。一旦遇到await Task.Delay(5),线程2000返回到线程池(据说,我正在尝试确认这一点),并等待Task.Delay(5)完成。同时,控制权返回给调用者,并且testAsync的第一个循环在线程2000上执行。
因此,在两个连续的代码行之间,线程返回到线程池,然后从那里回来。如何使用调试器确认这一点?可能使用线程调试器窗口?
为了更清楚地说明我的问题:foo正在线程2000上运行。有两种可能的情况:
1. 当它遇到await Task.Delay(5)时,线程2000会短暂地返回到线程池,控制权返回到调用者,在第二行执行(使用从线程池中取出的线程2000)。如果是这样,你无法轻松地检测到它,因为在线程池中的线程2000在两个连续的代码行之间的时间内。 2. 当它遇到await Task.Delay(5)时,线程2000不会返回到线程池,而是立即执行testAsync中从第二行开始的代码。
我想验证哪一个是真正发生的。

尝试使用await Task.Yield();代替await Task.Delay(5); - Ivan Stoev
@IvanStoev 没有区别。它不会改变它运行的线程,也不会在线程调试窗口中以不同的方式运行。 - user4205580
5
你为什么需要知道? - Enigmativity
3个回答

3

您可以将Thread.CurrentThread.ManagedThreadId打印到控制台。请注意,线程池可以自由地重用同一线程来运行其上的继续项,因此不能保证它会有所不同:

void Main()
{
    TestAsync().Wait();
}

public async Task FooAsync()
{
    int y = 0;
    await Task.Delay(5);
    Console.WriteLine($"After awaiting in FooAsync:
                        {Thread.CurrentThread.ManagedThreadId }");

    while (y < 5) y++;
}

public async Task TestAsync()
{
    Console.WriteLine($"Before awaiting in TestAsync:
        {Thread.CurrentThread.ManagedThreadId }");
    Task task = foo();
    int i = 0;
    while (i < 100)
    {
        var x = i++;
    }

    await task;
    Console.WriteLine($"After awaiting in TestAsync:
                        {Thread.CurrentThread.ManagedThreadId }");
}

你可以检查的另一件事是使用ThreadPool.GetAvailableThreads来确定是否已经分配了另一个工作线程:

async Task FooAsync()
{
    int y = 0;
    await Task.Delay(5);

    Console.WriteLine("Thread-Pool threads after first await:");
    int avaliableWorkers;
    int avaliableIo;
    ThreadPool.GetAvailableThreads(out avaliableWorkers, out avaliableIo);
    Console.WriteLine($"Available Workers: { avaliableWorkers}, 
                        Available IO: { avaliableIo }");

    while (y < 1000000000) y++;
}

async Task TestAsync()
{
    int avaliableWorkers;
    int avaliableIo;
    ThreadPool.GetAvailableThreads(out avaliableWorkers, out avaliableIo);

    Console.WriteLine("Thread-Pool threads before first await:");
    Console.WriteLine($"Available Workers: { avaliableWorkers}, 
                        Available IO: { avaliableIo }");
    Console.WriteLine("-------------------------------------------------------------");

    Task task = FooAsync();

    int i = 0;
    while (i < 100)
    {
        var x = i++;
    }

    await task;
}

在我的机器上,这将产生以下结果:
Thread-Pool threads before first await:
Available Workers: 1023, Available IO: 1000
----------------------------------------------
Thread-Pool threads after first await:
Available Workers: 1022, Available IO: 1000

这就是我目前正在做的事情,而且 Console.WriteLine 打印出 相同的线程 ID - 很可能是因为线程 2000 在 await Task.Delay(5) 处首先返回到线程池中,然后又被从线程池中取回来执行 testAsync 的第一个循环。不过,我想检查一下线程是否已经返回到线程池。 - user4205580
@user4205580 你为什么认为它没有返回值?那个线程会去哪里?您无法迭代线程池中的线程,因为这是一个实现细节。 - Yuval Itzchakov
我已经在代码中添加了注释。我只想知道它是否返回到线程池中。它不必返回到线程池中,对吧?我们不知道。 - user4205580
@user4205580 我不明白你的意思。如果不是线程池,他们会返回到哪里? - Yuval Itzchakov
@user4205580,请看我的编辑,或许能更清楚地解释。 - Yuval Itzchakov
显示剩余3条评论

3
你的假设有一个重大错误:
当线程2000遇到await Task.Delay(5)时,它会返回线程池。因为你还没有等待foo(),所以当线程2000遇到Task.Delay(5)时,它只是创建一个新的任务并返回到testAsync()(即int i = 0;)。然后进入while循环块,此时你才等待task。在这一点上,如果task尚未完成,并且假设其余代码已等待,则线程2000将返回到线程池。否则,如果task已经完成,则它将从foo()(即while (y<5) y++;)同步继续执行。
编辑:
那如果主方法调用了testAsync呢?
当同步方法调用并等待异步方法时,如果异步方法返回未完成的任务,则必须阻塞该线程:
void Main()
{
    var task = foo();
    task.Wait(); //Will block the thread if foo() is not completed.
}

请注意,在上述情况下,线程没有返回到线程池 - 它被操作系统完全挂起。
也许你可以举个例子,说明如何调用testAsync,以使2000线程返回到线程池?
假设2000线程是主线程,它无法返回到线程池。但是,您可以使用Task.Run(() => foo())在线程池上运行foo(),并且由于调用线程是主线程,另一个线程池线程将接管该任务。因此,以下代码:
static void Main(string[] args)
{
    Console.WriteLine("main started on thread {0}", Thread.CurrentThread.ManagedThreadId);
    var testAsyncTask = Task.Run(() => testAsync());
    testAsyncTask.Wait();
}
static async Task testAsync()
{
    Console.WriteLine("testAsync started on thread {0}", Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(1000);
    Console.WriteLine("testAsync continued on thread {0}", Thread.CurrentThread.ManagedThreadId);
}

在我的电脑上生成了以下输出:
main started on thread 1
testAsync started on thread 3
testAsync continued on thread 4
Press any key to continue . . .

第三个和第四个线程来自线程池并返回线程池。


如果任务尚未完成,并且假设代码的其余部分正在等待,我认为如果任务尚未完成,则控制权将返回到调用者,在2000线程上。如果等待testAsync,则它将返回另一个级别,在2000线程上。 - user4205580
@user4205580:“我认为如果任务还没有完成,控制权将返回给调用者”-确实如此!“如果等待testAsync,则会再次返回一个级别”-不完全是这样。当线程2000在foo()中遇到await Task.Delay(5);后恢复到testAsync时,它会同步执行testAsync,直到需要等待某些东西。第一次等待任何内容时,是在while块之后。 - shay__
是的,在执行到 await task 之后,它将会返回调用 callerOfTestAsync 并在线程2000上执行,因此线程2000不会返回到线程池。好吧,我刚刚测试过了。 - user4205580
线程2000在达到await task;时从未返回到线程池,此时它完全是同步的。在异步程序中,只有当堆栈上所有的await操作都等待未完成的任务时,线程才会返回到线程池。如果不需要等待任何东西,它将继续同步执行。 - shay__
1
当然,主线程不能返回到线程池。在控制台应用程序中,您必须在Main方法中阻塞任务。在这种情况下,如果任务未完成,则线程将被操作系统阻塞(而不是“线程2000将执行main方法的其余部分”,这是不正确的)。 - shay__
显示剩余4条评论

1
我想验证到底发生了什么。 由于调试器是用来模拟逻辑(同步)流程的,所以无法通过调试器来“验证” - 请参见 使用 Async 方法的调试器演练。要理解正在发生的事情(FYI 是您的情况(2)),您需要从 使用 Async 和 Await 进行异步编程 开始学习 await 的工作原理 - 异步方法中发生了什么 部分,异步程序中的控制流 和其他许多来源。 看看这段代码片段:
static void Main(string[] args)
{
    Task.Run(() =>
    {
        // Initial thread pool thread
        var t = testAsync();
        t.Wait();
    });
    Console.ReadLine();
}

如果我们将lambda设置为async并使用await t;而不是t.Wait();,这就是初始线程将返回到线程池的位置。正如我上面提到的,您无法通过调试器验证。但是请看上面的代码并进行逻辑思考 - 我们正在阻止初始线程,因此如果它不是空闲的,您的testAsyncfoo方法将无法恢复。但是它们确实可以,并且可以通过在await之后放置断点来轻松验证此操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接