簡體   English   中英

C# async/await with HttpClient GetAsync - 非異步調用方法

[英]C# async/await with HttpClient GetAsync - non-async calling method

這是從無法標記為異步的主 function 調用時的異步/等待 - function必須同步運行,但由於 HttpClient 是異步的,因此在某些時候,等待必須使事情正常停止。 我已經閱讀了很多關於.Result.Wait如何導致死鎖的內容,但這些是唯一真正使代碼同步的版本。

這是一個示例程序,大致遵循代碼的作用——注意循環中的 4 次嘗試——只有其中一次嘗試以正確的順序輸出數據。 這個結構有什么根本性的錯誤嗎/我不能使用示例 #3 嗎?

我能找到的最接近的例子是在這里,那是調用 function 可以異步的地方,而這個不能。 我試過將private static void Process()異步並使用Task.Run(async ()=> await Process());調用它。 但它仍然出現故障。 唯一始終如一的工作是Wait/Result ,它可能會死鎖,特別是我讀過的 HttpClient 。 有什么想法嗎?

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("1. Calling process at {0:mm.ss.fff}", DateTime.Now);

            // Call 1
            Process();

            Console.WriteLine("1. Calling process at {0:mm.ss.fff}", DateTime.Now);

            // Call 2
            Process();

            Console.ReadKey();
        }

        private static void Process()
        {
            Console.WriteLine("2. Calling CallAsyncTest at {0:mm.ss.fff}", DateTime.Now);

            for (int i = 1; i < 4; i++)
            {
                // Try 1 - doesn't work
                //CallAsyncTest(i);

                // Try 2 - doesn't work
                //Task.Run(async () => await CallAsyncTest(i));

                // Try 3 - but works not recommended
                CallAsyncTest(i).Wait();

                // Try 4 - doesn't work
                //CallAsyncTest(i).ConfigureAwait(false); ;
            }
        }

        private static async Task CallAsyncTest(int i)
        {
            Console.WriteLine("{0}. Calling await AsyncTest.Start at {1:mm.ss.fff}", i + 2, DateTime.Now);

            var x = await AsyncTest.Start(i);
        }
    }

    public class AsyncTest
    {
        public static async Task<string> Start(int i)
        {
            Console.WriteLine("{0}. Calling await Post<string> at {1:mm.ss.fff}", i + 3, DateTime.Now);

            return await Post<string>(i);
        }

        private static async Task<T> Post<T>(int i)
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (HttpClient httpClient = new HttpClient(new HttpClientHandler()))
            {
                using (HttpResponseMessage response = await httpClient.GetAsync("https://www.google.com"))
                {
                    using (HttpContent content = response.Content)
                    {
                        string responseString = await content.ReadAsStringAsync();

            

        Console.WriteLine("response");
                }
            }

        }

        return default(T);
    }
}

但這些是唯一真正使代碼同步的版本

確切地說,您不應該僅出於這個原因使用它。 您不想在執行 I/O 時鎖定您的主線程,否則您的應用程序在最字面意義上就是垃圾。

如果您出於某種原因不想讓整個應用程序具有async感知能力,您仍然可以使用適當的async模式。 請記住, await只不過是Task class 已經為您提供的語法糖:一種在任務完成后繼續執行的方法。

因此,與其讓進程 function 鎖定主線程, Task.WhenAll .ContinueWith()如果它們是應該並行運行)並立即返回。 然后,continuation 負責使用接收到的數據(如果成功)或錯誤信息(如果失敗)更新您的 UI。

function 必須同步運行

正如其他人指出的那樣,最好的解決方案是 go async all the way 但我假設有充分的理由說明這是不可能的。

在某些時候, await 必須讓事情優雅地停止。

在某些時候,您的代碼將不得不阻塞異步代碼,是的。

我已經閱讀了很多有關 .Result 或 .Wait 如何導致死鎖的內容,但這些是唯一真正使代碼同步的版本。

正確的。 阻塞異步代碼是使其同步的唯一方法。

它實際上與ResultWait本身無關——它是任何一種阻塞。 並且在某些情況下阻塞異步代碼會導致死鎖 具體來說,當所有這些條件都為真時:

  1. 異步代碼捕獲上下文 await默認情況下確實捕獲上下文。
  2. 調用代碼會阻塞該上下文中的線程。
  3. 上下文一次只允許一個線程。 例如,UI 線程上下文或遺留 ASP.NET 請求上下文一次只允許一個線程。

控制台應用程序和 ASP.NET 核心應用程序沒有上下文,因此在這些場景中不會發生死鎖。

唯一始終如一的工作是 Wait/Result ,它可以死鎖......有什么想法嗎?

您可以選擇一些技巧來阻止異步代碼。

其中之一就是直接阻止。 這適用於 Console / ASP.NET Core 應用程序(因為它們沒有會導致死鎖的上下文)。 我建議在這種情況下使用GetAwaiter().GetResult()以避免在使用Result / Wait()時出現異常包裝器:

CallAsyncTest(i).GetAwaiter().GetResult();

但是,如果應用程序是 UI 應用程序或舊版 ASP.NET 應用程序,則該方法將不起作用。 在這種情況下,您可以將異步工作推送到線程池線程並阻塞線程。 線程池線程在上下文之外運行,因此它避免了死鎖,但是這個 hack 意味着CallAsyncTest必須能夠在線程池線程上運行 - 不能訪問 UI 元素或HttpContext.Current或任何其他依賴於上下文的東西,因為上下文不會在那里:

Task.Run(() => CallAsyncTest(i)).GetAwaiter().GetResult();

沒有適用於所有情況的解決方案。 如果沒有上下文(例如,控制台應用程序),則直接阻止會起作用。 如果代碼可以在任意線程池線程上運行,則在Task.Run上阻塞會起作用。 還有一些其他的 hack 可用,但這兩個是最常見的。

這就是其他嘗試失敗的原因。

這個開始任務但根本不等待:

// Try 1 - doesn't work
CallAsyncTest(i);

這一個在線程池線程中啟動任務但根本不等待:

// Try 2 - doesn't work
Task.Run(async () => await CallAsyncTest(i));

“Try 4”與“Try 1”完全相同。 ConfigureAwait的調用不執行任何操作,因為沒有await配置。 所以它也只是啟動任務但根本不等待:

// Try 4 - doesn't work
CallAsyncTest(i).ConfigureAwait(false);

為什么不使 Process 方法異步,然后使用正常的異步等待模式?

Process 簽名將是

private static async Task Process()

這將允許您使用await CallAsyncTest(i)

你會在 main 方法上等待它,就像這樣:

Process.GetAwaiter().GetResult();

這相當於 C# 7.1 中引入的 async main 的實現

https://learn.microsoft.com/en-us/do.net/csharp/language-reference/proposals/csharp-7.1/async-main#detailed-design

暫無
暫無

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

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