【问题标题】:System.InvalidOperationException when using GetAwaiter().GetResult() with ServiceBusReceiver.PeekMessagesAsync将 GetAwaiter().GetResult() 与 ServiceBusReceiver.PeekMessagesAsync 一起使用时出现 System.InvalidOperationException
【发布时间】:2021-06-10 00:00:19
【问题描述】:

上下文

我们使用GetAwaiter().GetResult() 是因为PowerShell 的Cmdlet.ProcessRecord() 不支持异步/等待。

代码示例

class Program
{
    static async Task Main(string[] args)
    {
        var topicPath = "some-topic";
        var subscriptionName = "some-subscription";
        var connectionString = "some-connection-string";

        var subscriptionPath = EntityNameHelper.FormatSubscriptionPath(
            topicPath,
            subscriptionName
        );

        var serviceBusClient = new ServiceBusClient(connectionString);
        var receiver = serviceBusClient.CreateReceiver(queueName: subscriptionPath);

        // This one works. :-) 
        await foreach (var item in GetMessages(receiver, maxMessagesPerFetch: 5))
        {
            Console.WriteLine("async/await: " + item);
        }

        // This one explodes.
        var enumerator = GetMessages(receiver, maxMessagesPerFetch: 5).GetAsyncEnumerator();
        while (enumerator.MoveNextAsync().GetAwaiter().GetResult())
        {
            // Unhandled exception. System.InvalidOperationException: Operation is not valid due to the current state of the object.
            //    at NonSync.IAsyncEnumerable.Program.GetMessages(ServiceBusReceiver receiver, Int32 maxMessagesPerFetch)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
            //    at NonSync.IAsyncEnumerable.Program.Main(String[] args) in C:\dev\mediavalet\MediaValet.Learning\entropy\NonSynchronousDotNet\NonSync.IAsyncEnumerable\Program.cs:line 42
            //    at NonSync.IAsyncEnumerable.Program.<Main>(String[] args)
            Console.WriteLine("GetAwaiter().GetResult(): " + enumerator.Current);
        }
    }

    public static async IAsyncEnumerable<string> GetMessages(
        ServiceBusReceiver receiver,
        int maxMessagesPerFetch
    )
    {
        yield return "Foo";
        var messages = await receiver.PeekMessagesAsync(maxMessagesPerFetch);
        yield return "Bar";
    }
}

问题

这里发生了什么?我们如何在不更改 GetMessages 的情况下修复它?

【问题讨论】:

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


    【解决方案1】:

    根据ValueTask&lt;TResult&gt;结构的文档:

    不应在ValueTask&lt;TResult&gt; 实例上执行以下操作:

    • 多次等待实例。
    • 多次致电AsTask。
    • 在操作尚未完成或多次使用时使用.Result 或.GetAwaiter().GetResult()。
    • 使用这些技术中的一种以上来使用实例。

    如果您执行上述任何操作,则结果未定义。

    您可以做的是使用AsTask 方法将ValueTask&lt;bool&gt; 转换为Task&lt;bool&gt;:

    while (enumerator.MoveNextAsync().AsTask().GetAwaiter().GetResult())
    

    【讨论】:

      【解决方案2】:

      此答案通过代码示例补充了 Theodor 的答案。我们的具体问题是我们在ValueTask 完成之前调用了GetResult()。文档指出这是不允许的:

      ValueTask 实例只能等待一次,在实例完成之前,消费者可能无法读取 Result。 如果这些限制不可接受,请通过调用 AsTask 将 ValueTask 转换为 Task。 (强调)。

      using System;
      using System.Collections.Generic;
      using System.Threading.Tasks;
      
      var enumerator = GetAsyncEnumerable().GetAsyncEnumerator();
      
      while (true)
      {
          var moveNext = enumerator.MoveNextAsync();
          var moveNextAwaiter = moveNext.GetAwaiter();
      
          Console.WriteLine("ValueTask.IsCompleted: {0}", moveNext.IsCompleted);
      
          try
          {
              if (moveNextAwaiter.GetResult())
              {
                  Console.WriteLine("IAsyncEnumerator.Current: {0}", enumerator.Current);
                  continue;
              }
      
              Console.WriteLine("Done! We passed the end of the collection.");
              break;
          }
          catch (InvalidOperationException)
          {
              Console.WriteLine("Boom! GetResult() before the ValueTask completed.");
              continue;
          }
      }
      
      async IAsyncEnumerable<int> GetAsyncEnumerable()
      {
          yield return 1;
          await Task.Delay(1000);
          yield return 2; // <---- We never access this, because GetResult() explodes.
          yield return 3;
      }
      

      输出:

      ValueTask.IsCompleted: True
      IAsyncEnumerator.Current: 1
      
      ValueTask.IsCompleted: False
      Boom! GetResult() before the ValueTask completed.
      
      ValueTask.IsCompleted: True
      IAsyncEnumerator.Current: 3
      
      ValueTask.IsCompleted: True
      Done! We passed the end of the collection.
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-12-24
        • 2016-07-25
        • 2016-04-05
        • 1970-01-01
        • 2021-02-28
        • 2017-10-19
        • 2017-12-04
        • 2022-01-04
        相关资源
        最近更新 更多