繁体   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