簡體   English   中英

使用 thread.sleep() 時,異步編程如何與線程一起工作?

[英]How does Async programming work with Threads when using thread.sleep()?

假設/前奏:

  1. 在前面的問題中,我們注意到 thread.sleep 會阻塞線程,請參閱: 何時使用 Task.Delay,何時使用 Thread.Sleep? .
  2. 我們還注意到控制台應用程序具有三個線程:主線程、GC 線程和終結器線程 IIRC。 所有其他線程都是調試器線程。
  3. 我們知道 async 不會啟動新線程,而是在同步上下文中運行,“僅當方法處於活動狀態時才在線程上使用時間”。 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

設置:
在示例控制台應用程序中,我們可以看到兄弟代碼和父代碼都不會受到對 thread.sleep 的調用的影響,至少在調用 await 之前(如果進一步則未知)。

var sw = new Stopwatch();
sw.Start();
Console.WriteLine($"{sw.Elapsed}");
var asyncTests = new AsyncTests();

var go1 = asyncTests.WriteWithSleep();
var go2 = asyncTests.WriteWithoutSleep();

await go1;
await go2;
sw.Stop();
Console.WriteLine($"{sw.Elapsed}");
        
Stopwatch sw1 = new Stopwatch();
public async Task WriteWithSleep()
{
    sw1.Start();
    await Task.Delay(1000);
    Console.WriteLine("Delayed 1 seconds");
    Console.WriteLine($"{sw1.Elapsed}");
    Thread.Sleep(9000);
    Console.WriteLine("Delayed 10 seconds");
    Console.WriteLine($"{sw1.Elapsed}");
    sw1.Stop();
}
public async Task WriteWithoutSleep()
{
    await Task.Delay(3000);
    Console.WriteLine("Delayed 3 second.");
    Console.WriteLine($"{sw1.Elapsed}");
    await Task.Delay(6000);
    Console.WriteLine("Delayed 9 seconds.");
    Console.WriteLine($"{sw1.Elapsed}");
}

問題:如果線程在thread.sleep期間被阻止執行,它如何繼續處理父級和兄弟級? 有人回答說它是后台線程,但我沒有看到多線程后台線程的證據。 我錯過了什么?

Task.Delay方法基本上是這樣實現的(簡化¹):

public static Task Delay(int millisecondsDelay)
{
    var tcs = new TaskCompletionSource();
    _ = new Timer(_ => tcs.SetResult(), null, millisecondsDelay, -1);
    return tcs.Task;
}

TaskSystem.Threading.Timer組件的回調中完成,根據文檔,此回調在ThreadPool線程上調用:

該方法不會在創建計時器的線程上執行; 它在系統提供的ThreadPool線程上執行。

因此,當您等待Task.Delay方法返回的任務時, await之后的繼續在ThreadPool上運行。 ThreadPool通常有多個線程按需立即可用,因此如果您一次創建 2 個任務,引入並發性和並行性並不難,就像您在示例中所做的那樣。 默認情況下,控制台應用程序的主線程沒有配備SynchronizationContext ,因此沒有適當的機制來防止觀察到的並發。

¹僅用於演示目的。 Timer引用未存儲在任何地方,因此它可能在調用回調之前被垃圾收集,導致Task永遠不會完成。

我沒有看到多線程后台線程的證據。 我錯過了什么?

可能你找錯地方了,或者使用了錯誤的工具。 有一個方便的屬性可能對您有用,形式為Thread.CurrentThread.ManagedThreadId 根據文檔

線程的 ManagedThreadId 屬性值用於在其進程中唯一標識該線程。

ManagedThreadId 屬性的值不隨時間變化

這意味着在同一線程上運行的所有代碼將始終看到相同的ManagedThreadId值。 如果您在代碼中添加一些額外的WriteLine ,您將能夠看到您的任務在其生命周期中可能在多個不同的線程上運行。 一些異步應用程序甚至完全有可能讓它們的所有任務在同一個線程上運行,盡管在正常情況下您可能不會在代碼中看到這種行為。

這是我機器上的一些示例輸出,不保證在你的機器上是相同的,也不一定在同一應用程序的連續運行中是相同的輸出。

00:00:00.0000030
 * WriteWithSleep on thread 1 before await
 * WriteWithoutSleep on thread 1 before first await
 * WriteWithSleep on thread 4 after await
Delayed 1 seconds
00:00:01.0203244
 * WriteWithoutSleep on thread 5 after first await
Delayed 3 second.
00:00:03.0310891
 * WriteWithoutSleep on thread 6 after second await
Delayed 9 seconds.
00:00:09.0609263
Delayed 10 seconds
00:00:10.0257838
00:00:10.0898976

在線程上運行任務的業務由TaskScheduler處理。 您可以編寫一個強制代碼為單線程的代碼,但這通常不是一件有用的事情。 默認調度程序使用線程池,因此可以在許多不同的線程上運行此類任務。

我不接受我自己的答案,我會接受別人的答案,因為他們幫助我解決了這個問題。 首先,在我的問題的上下文中,我使用的是 async Main。 在 Theodor 和 Rook 的答案之間很難做出選擇。 但是,Rook 的回答為我提供了幫助我釣魚的一件事:Thread.CurrentThread.ManagedThreadId

這些是我運行代碼的結果:

1 00:00:00.0000767
Not Delayed.
1 00:00:00.2988809
Delayed 1 second.
4 00:00:01.3392148
Delayed 3 second.
5 00:00:03.3716776
Delayed 9 seconds.
5 00:00:09.3838139
Delayed 10 seconds
4 00:00:10.3411050
4 00:00:10.5313519

我注意到這里有 3 個線程,初始線程 (1) 提供第一個調用方法和 WriteWithSleep() 的一部分,直到 Task.Delay 被初始化並稍后等待。 當 Task.Delay 被帶回線程 1 時,所有內容都在線程 4 上運行,而不是線程 1 上的 main 和 WriteWithSleep 的其余部分。
WriteWithoutSleep 使用自己的 Thread(5)。

所以我的錯誤是認為只有 3 個線程。 我相信這個問題的答案: https ://stackoverflow.com/questions/3476642/why-does-this-simple-net-console-app-have-so-many-threads#:~:text=You%20should %20only%20see%20three,see%20are%20debugger%2Drelated%20threads

但是,該問題可能不是異步的,或者可能沒有考慮線程池中的這些額外工作線程。

感謝大家幫助解決這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM