【问题标题】:How can I implement both an async method and its synchronous counterpart? [duplicate]如何实现异步方法及其同步方法? [复制]
【发布时间】:2020-04-23 16:04:31
【问题描述】:

我有一个方法,例如Task<string> GetContentAsync(string url),而我的控制台应用程序还没有准备好在内部利用 TPL,但也许以后会。

我怎样才能轻松地为此(或其他)方法编写同步包装器(不是替代实现)?

【问题讨论】:

  • @Servy 这与 UI 响应无关,因此不是该问题的重复。
  • @MichaelJ.Gray 确实,它不是 exact 重复项,但是该问题的答案回答了这个问题,这使得它适合使用 close 功能。
  • @Servy 考虑到您需要在该答案中实现一些消息泵送系统,这在控制台应用程序中基本上没有用处,我会说它不恰当地解决了过度设计的问题解决方案。通过访问Task.Result,控制台应用程序中的继续死锁没有问题。
  • @MichaelJ.Gray 1) 这是一个非常有用的工具,即使在控制台应用程序中也是如此。事实上,如果您阅读了该帖子中链接的文章,那么创建它的全部原因 就是在控制台应用程序中创建同步上下文的一种方式。接下来,虽然默认情况下控制台应用程序没有同步上下文,因此不会死锁,但您可以在控制台应用程序中创建一个同步上下文,它可以制作 i> 这个代码死锁,所以控制台应用程序不能完全忽略这个问题。
  • @Servy 除了我设置上下文的部分,它仍然可以正常工作。

标签: c# .net task-parallel-library


【解决方案1】:

如果您的库需要同时实现同步和异步成员,那么您需要同时实现这两个成员。没有快捷方式(假设这是一个可重用的库)。

public async Task<string> GetContentAsync(string url)
{
  ... // Logic here, e.g., using HttpClient
}

public string GetContent(string url)
{
  ... // Duplicate logic here, e.g., using WebClient
}

逻辑的重复当然是不幸的,但如果你试图走捷径,你实际上会陷入更糟糕的境地。对于 SO 答案来说,“为什么”的细节有点长,但 Stephen Toub 涵盖了在他的经典博客文章 "Should I expose synchronous wrappers for asynchronous methods?""Should I expose asynchronous wrappers for synchronous methods?" 包装时出现的问题

顺便说一句,这两个问题的答案都是“不”。另请参阅my SO answer here

【讨论】:

  • 我阅读了您的答案,它们看起来不错,但我想为另一种方法编写一个包装器,而不是使用同步方法调用的第二个实现,就像您使用 HttpClientWebClient 一样对于另一个。我想这有点模棱两可,人们可能不会将“包装器”明确理解为包装器,它可能会建议“替代实现”,具体取决于人的思维过程。
  • @MichaelJ.Gray:我知道你想要一个简单的包装。我的观点是你不能拥有它。 :)
  • 我不想要一个简单的包装,我想要一个包装期。我不想要替代实现。这就是问题的性质。通过使用代码生成来为几乎所有情况制定通用解决方案是非常有可能的,即使对于非异步上下文也是如此。尽管这比仅仅为方法制作包装器或将可用于制作包装器的模式组合在一起要多得多。但我确实说,如果您的答案不是严格地与制作包装器相关并且只是在考虑启动和运行异步功能,那么您的答案将满足这个问题。
  • @MichaelJ.Gray:我认为代码生成是一个很好的未来方法。我不会说它现在是可行的,即使对于大型代码库,但也许在 Roslyn 的几个版本之后(有一个 interesting project here 目前仍处于 NDA 之下)。也就是说,我不会将代码生成视为“包装器”。
  • 那个项目看起来很棒。一个很好的发现。
【解决方案2】:

这是一个测试用例的代码,表明可以通过一种非常简单的方式完成此操作。为了演示,我还实现了一个GetContentAsync 方法。

using System.IO;
using System.Net;
using System.Threading.Tasks;

namespace AsyncTestCase.Driver
{
    public class AsyncTestCase
    {
        public AsyncTestCase()
        { }

        public string GetContent(string url)
        {
            Task<string> task = this.GetContentAsync(url);
            return task.Result;
        }

        public async Task<string> GetContentAsync(string url)
        {
            HttpWebRequest request = HttpWebRequest.CreateHttp(url);
            HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;

            using (Stream stream = response.GetResponseStream())
            {
                using (TextReader reader = new StreamReader(stream))
                {
                    string content = await reader.ReadToEndAsync();
                    return content;
                }
            }
        }
    }
}

这里的代码表明它工作得很好并且很容易使用:

namespace AsyncTestCase.Driver
{
    internal static class Program
    {
        private static void Main()
        {
            AsyncTestCase test = new AsyncTestCase();
            string content = test.GetContent("http://www.google.com");
        }
    }
}

【讨论】:

  • 这通常是一个非常糟糕的主意。有关原因的详细说明,请参阅Should I expose synchronous wrappers for asynchronous methods?
  • 所以,无论谁投了反对票。您能否提供有关此答案如何“无用”的信息。
  • 目前对此答案有评论,解释了为什么这是一个糟糕的答案,并链接到一篇深入解释使用此答案可能导致的非常严重问题的文章。该评论也有 3 票赞成,表明其他几位读者认为它特别相关。该评论也是在您发表评论前一个多小时发布的,所以我认为到现在您已经有足够的机会看到它了。
  • 答案不正确,无法按您的预期发挥作用,虽然它可能对某些人有用,但建议使用它是非常有害,因为非常很大一部分用户会在使用它时导致严重的问题(即,使他们的整个程序陷入僵局)。我已将您链接到描述代码死锁原因的多个资源;在 cmets 中充分描述的信息太多;我试图这样做,但显然摘要对你来说还不够。我建议您阅读这篇文章,详细解释为什么这是不好的
  • 但是,你能证明吗?你基本上是在要求我们把你的话当作福音,并不断提出不相关的 UI 问题。这不适用于任何类型的 UI 应用程序,它适用于没有同步上下文行为的基本控制台应用程序。您现在已经对我与该主题相关的所有答案投了反对票,并对其他有您根本不同意的有用答案的人投了反对票。这是对投票系统的滥用,因为答案在问题的上下文中很有用。
猜你喜欢
  • 2015-03-29
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
  • 1970-01-01
  • 2019-05-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多