将重要代码转换为新的 .NET 异步模式 - 如何处理 Yield 循环

发布于 2025-01-07 00:27:59 字数 2189 浏览 1 评论 0原文

我正在编写一个与 Azure 表存储一起使用的库。基本模式是给定的 HTTP 请求在内容流中返回多个结果,并在标头中返回指向下一组结果的指针。当从流中读取结果时,就会产生结果。我正在使用 System.Net.Http 库(以前是 Microsoft.Net.Http),它在最新版本中删除了 HttpClient.Send 的同步版本和其他同步方法。新版本使用任务。我以前使用过任务,但不是用于如此复杂的事情,而且我很难开始。

已转换为异步模式的调用有:HttpClient.Send、response.Context.ContentReadSteam。我已经清理了代码,以便显示重要的部分。

var queryUri = _GetTableQueryUri(tableServiceUri, tableName, query, null, null, timeout);
while(true) {
    var continuationParitionKey = "";
    var continuationRowKey = "";
    using (var request = GetRequest(queryUri, null, action.Method, azureAccountName, azureAccountKey))
    {
        using (var client = new HttpClient())
        {
            using (var response = client.Send(request, HttpCompletionOption.ResponseHeadersRead))
            {
                continuationParitionKey = // stuff from headers
                continuationRowKey = // stuff from headers

                using (var reader = XmlReader.Create(response.Content.ContentReadStream))
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry" && reader.NamespaceURI == "http://www.w3.org/2005/Atom")
                        {
                            yield return XElement.ReadFrom(reader) as XElement;
                        }
                    }
                    reader.Close();
                }
            }
        }
    }
    if (continuationParitionKey == null && continuationRowKey == null)
        break;

    queryUri = _GetTableQueryUri(tableServiceUri, tableName, query, continuationParitionKey, continuationRowKey, timeout);
}

下面是我已成功转换的一个示例。

client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ContinueWith(task =>
    {
        using (var response = task.Result)
        {
            if (response.StatusCode == HttpStatusCode.Created && action == HttpMethod.Post)
            {
                return XElement.Load(response.Content.ReadAsStreamAsync().Result);
            }
        }
    });

有人对如何将循环/产量转换为新模式有任何建议吗?

谢谢! 埃里克

I am writing a library to work with Azure Table Storage. The basic pattern is that a given HTTP request returns a number results in the content stream, and a pointer to the next set of results in the headers. As the results are read from the stream, they are yielded. I am using the System.Net.Http library (previously Microsoft.Net.Http), which in the latest version removed the synchronous version of HttpClient.Send and other synchronous methods. The new version uses Tasks. I've used Tasks before, but not for something this complex, and I am having a hard time getting a start.

The calls that have been converted to the async pattern are: HttpClient.Send, response.Context.ContentReadSteam. I've cleaned up the code so that the important parts are shown.

var queryUri = _GetTableQueryUri(tableServiceUri, tableName, query, null, null, timeout);
while(true) {
    var continuationParitionKey = "";
    var continuationRowKey = "";
    using (var request = GetRequest(queryUri, null, action.Method, azureAccountName, azureAccountKey))
    {
        using (var client = new HttpClient())
        {
            using (var response = client.Send(request, HttpCompletionOption.ResponseHeadersRead))
            {
                continuationParitionKey = // stuff from headers
                continuationRowKey = // stuff from headers

                using (var reader = XmlReader.Create(response.Content.ContentReadStream))
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry" && reader.NamespaceURI == "http://www.w3.org/2005/Atom")
                        {
                            yield return XElement.ReadFrom(reader) as XElement;
                        }
                    }
                    reader.Close();
                }
            }
        }
    }
    if (continuationParitionKey == null && continuationRowKey == null)
        break;

    queryUri = _GetTableQueryUri(tableServiceUri, tableName, query, continuationParitionKey, continuationRowKey, timeout);
}

An example of one that I have successfully converted is below.

client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ContinueWith(task =>
    {
        using (var response = task.Result)
        {
            if (response.StatusCode == HttpStatusCode.Created && action == HttpMethod.Post)
            {
                return XElement.Load(response.Content.ReadAsStreamAsync().Result);
            }
        }
    });

Does anyone have any suggestions on how to convert the loop/yield to the new pattern?

Thanks!
Erick

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

偏爱你一生 2025-01-14 00:27:59

正如您所发现的,async 目前并不能与 yield 配合使用。尽管它们进行类似的代码转换,但目标却截然不同。

有两种解决方案:一种是提供缓冲区并使用生产者/消费者类型的方法。 System.Tasks.Dataflow.dll 对于管理复杂代码中的缓冲区非常有用。

另一个解决方案是编写一个“异步枚举器”。这在概念上更接近于您的代码应该执行的操作,但此解决方案比生产者/消费者解决方案复杂得多。

“异步枚举器”类型在 Rx 上的此视频,您可以从 一个实验性的 Rx 包(请注意,尽管这是由 Rx 团队完成的,但它实际上并没有使用 Rx)。

As you've discovered, async doesn't work the greatest with yield right now. Even though they do similar code transformations, the goal is quite different.

There are two solutions: one is to provide a buffer and use a producer/consumer type of approach. System.Tasks.Dataflow.dll is useful for managing buffers in complex code.

The other solution is to write an "async enumerator." This is conceptually closer to what your code should be doing, but this solution is much more complex than the producer/consumer solution.

The "async enumerator" type is discussed a bit in this video on Rx, and you can download it from an experimental Rx package (note that even though this is done by the Rx team, it actually does not use Rx).

昨迟人 2025-01-14 00:27:59

我建议将 Loop\yield 转换为某种形式的输出队列,例如 本文使用 BlockingCollection。因此,方法的调用者为您提供了一个队列来将结果推送到其中。

队列很方便,因为它将生产者与消费者解耦,但这只是一种选择。更一般地说,调用者为您提供一个回调,以便为您获取的每个结果执行。该回调应该是异步的,例如,它可以启动另一个任务。

I'd suggest to convert the loop\yield into some form of output queue, for example, like in this article using a BlockingCollection<T>. So the caller of your method provides you with a queue to push results to.

Queue is convenient, because it decouples producer from consumers, but it is just one option. More generally, the caller provides you with a callback to be executed for every result you fetch. That callback should be asyncronous, it can start another task, for example.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文