Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/304.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我怎样才能让‘等待…;’使用“yield return”(即在迭代器方法中)?_C#_Ado.net_Async Await_Generator_Yield Return - Fatal编程技术网

C# 我怎样才能让‘等待…;’使用“yield return”(即在迭代器方法中)?

C# 我怎样才能让‘等待…;’使用“yield return”(即在迭代器方法中)?,c#,ado.net,async-await,generator,yield-return,C#,Ado.net,Async Await,Generator,Yield Return,我的现有代码类似于: IEnumerable<SomeClass> GetStuff() { using (SqlConnection conn = new SqlConnection(connectionString)) using (SqlCommand cmd = new SqlCommand(sql, conn) { conn.Open(); SqlDataReader reader = cmd.ExecuteReade

我的现有代码类似于:

IEnumerable<SomeClass> GetStuff()
{
    using (SqlConnection conn = new SqlConnection(connectionString))
    using (SqlCommand cmd = new SqlCommand(sql, conn)
    {
        conn.Open();
        SqlDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            SomeClass someClass = f(reader); // create instance based on returned row
            yield return someClass;
        }
    } 
}
编译器通知我,
await
只能在标记为
async
的方法中使用,并建议我将方法签名修改为:

async Task<IEnumerable<SomeClass>> GetStuff()
异步任务GetStuff() 但是,这样做会使
GetStuff()
无法使用,因为:

GetStuff()
的主体不能是迭代器块,因为
Task
不是迭代器接口类型

我确信我缺少异步编程模型的一个关键概念

问题:

  • 我可以在迭代器中使用
    ReadAsync()
    ?怎么做
  • 我如何以不同的方式思考异步范例,以便了解它在这种情况下是如何工作的

    • 问题是你所问的实际上没有多大意义
      IEnumerable
      是一个同步接口,返回
      Task
      对您没有多大帮助,因为不管发生什么情况,某些线程都必须阻止等待每个项目

      实际上,您想要返回的是
      IEnumerable
      的一些异步替代方案:类似于
      IObservable
      、来自TPL数据流的数据流块或计划添加到C#8.0/.Net Core 3.0中的
      IAsyncenuerable
      。(同时,也有一些人控制着它。)

      使用TPL数据流,一种方法是:

      ISourceBlock<SomeClass> GetStuff() {
          var block = new BufferBlock<SomeClass>();
      
          Task.Run(async () =>
          {
              using (SqlConnection conn = new SqlConnection(connectionString))
              using (SqlCommand cmd = new SqlCommand(sql, conn))
              {
                  await conn.OpenAsync();
                  SqlDataReader reader = await cmd.ExecuteReaderAsync();
                  while (await reader.ReadAsync())
                  {
                      SomeClass someClass;
                      // Create an instance of SomeClass based on row returned.
                      block.Post(someClass);
                  }
                  block.Complete();
              } 
          });
      
          return block;
      }
      
      ISourceBlock GetStuff(){
      var block=新的缓冲块();
      Task.Run(异步()=>
      {
      使用(SqlConnection conn=newsqlconnection(connectionString))
      使用(SqlCommand cmd=newsqlcommand(sql,conn))
      {
      等待连接OpenAsync();
      SqlDataReader=await cmd.ExecuteReaderAsync();
      while(等待reader.ReadAsync())
      {
      某类某类;
      //根据返回的行创建SomeClass的实例。
      block.Post(someClass);
      }
      block.Complete();
      } 
      });
      返回块;
      }
      
      您可能希望将错误处理添加到上述代码中,但否则,它应该可以工作,并且将是完全异步的


      然后,代码的其余部分也将异步使用返回块中的项,可能使用
      ActionBlock

      不,您当前不能将async与迭代器块一起使用。正如斯维克所说,要做到这一点,您需要像
      IAsyncEnumerable
      这样的东西

      如果您有返回值
      Task
      ,则表示函数返回单个
      Task
      对象,该对象一旦完成,将为您提供一个完整格式的IEnumerable(此枚举中没有任务异步的空间)。任务对象完成后,调用方应该能够同步迭代它在可枚举表中返回的所有项

      下面是一个返回
      任务
      的解决方案。通过这样做,您可以从async中获得很大的好处:

      async Task<IEnumerable<SomeClass>> GetStuff()
      {
          using (SqlConnection conn = new SqlConnection(""))
          {
              using (SqlCommand cmd = new SqlCommand("", conn))
              {
                  await conn.OpenAsync();
                  SqlDataReader reader = await cmd.ExecuteReaderAsync();
                  return ReadItems(reader).ToArray();
              }
          }
      }
      
      IEnumerable<SomeClass> ReadItems(SqlDataReader reader)
      {
          while (reader.Read())
          {
              // Create an instance of SomeClass based on row returned.
              SomeClass someClass = null;
              yield return someClass;
          }
      }
      
      …以及一个示例用法:

      async void Caller()
      {
          // Calls get-stuff, which returns immediately with a Task
          Task<IEnumerable<SomeClass>> itemsAsync = GetStuff();
          // Wait for the task to complete so we can get the items
          IEnumerable<SomeClass> items = await itemsAsync;
          // Iterate synchronously through the items which are all already present
          foreach (SomeClass item in items)
          {
              Console.WriteLine(item);
          }
      }
      
      async void Caller()
      {
          // Synchronously get a list of Tasks
          IEnumerable<Task<SomeClass>> items = GetStuff();
          // Iterate through the Tasks
          foreach (Task<SomeClass> itemAsync in items)
          {
              // Wait for the task to complete. We need to wait for 
              // it to complete before we can know if it's the end of
              // the sequence
              SomeClass item = await itemAsync;
              // End of sequence?
              if (item == null) 
                  break;
              Console.WriteLine(item);
          }
      }
      
      async void Caller()
      {
      //同步获取任务列表
      IEnumerable items=GetStuff();
      //迭代完成任务
      foreach(项目中的任务项异步)
      {
      //等待任务完成。我们需要等待
      //在我们知道它是否是世界末日之前,它必须完成
      //序列
      SomeClass项=等待项异步;
      //序列结束?
      如果(项==null)
      打破
      控制台写入线(项目);
      }
      }
      

      在这种情况下,
      GetStuff
      会立即返回一个可枚举项,其中可枚举项中的每个项都是一个任务,完成后将呈现一个
      SomeClass
      对象。这种方法有一些缺陷。首先,枚举同步返回,所以在它返回时,我们实际上不知道结果中有多少行,这就是为什么我将其设置为无限序列。这是完全合法的,但也有一些副作用。我需要使用
      null
      来表示任务无限序列中有用数据的结束。第二,您必须小心如何迭代它。您需要向前迭代,并且需要等待每一行,然后再迭代到下一行。您还必须仅在所有任务完成后处置迭代器,以便GC在使用完连接之前不会收集连接。由于这些原因,这不是一个安全的解决方案,我必须强调,我把它包括进来是为了帮助回答您的第二个问题。

      严格地说是异步迭代器的(或者有可能)根据我的经验,在
      SqlCommand
      的上下文中,我注意到代码的同步版本远远优于它的
      async
      对应版本。在速度和内存消耗方面

      也许,对这一观察持保留态度,因为测试的范围仅限于我的机器和本地SQL Server实例

      别误会,在适当的环境下,.NET环境中的范例非常简单、强大和有用。然而,经过很多努力,我不相信数据库访问是一个合适的用例。当然,除非您需要同时执行多个命令,在这种情况下,您可以简单地使用来同时发出命令

      我更倾向于考虑以下因素:

      • 使SQL工作单元保持小型、简单和可组合(即,使您的SQL执行“便宜”)
      • 避免在SQL Server上执行可以向上游推送到应用程序级别的工作。排序就是一个很好的例子
      • 最重要的是,按比例测试SQL代码并查看统计信息IO输出/执行计划。一个在10k记录下快速运行的查询,当有1M记录时,可能(也可能会)表现完全不同
      你可以
      IEnumerable<Task<SomeClass>> GetStuff()
      {
          using (SqlConnection conn = new SqlConnection(""))
          {
              using (SqlCommand cmd = new SqlCommand("", conn))
              {
                  conn.Open();
                  SqlDataReader reader = cmd.ExecuteReader();
                  while (true)
                      yield return ReadItem(reader);
              }
          }
      }
      
      async Task<SomeClass> ReadItem(SqlDataReader reader)
      {
          if (await reader.ReadAsync())
          {
              // Create an instance of SomeClass based on row returned.
              SomeClass someClass = null;
              return someClass;
          }
          else
              return null; // Mark end of sequence
      }
      
      async void Caller()
      {
          // Synchronously get a list of Tasks
          IEnumerable<Task<SomeClass>> items = GetStuff();
          // Iterate through the Tasks
          foreach (Task<SomeClass> itemAsync in items)
          {
              // Wait for the task to complete. We need to wait for 
              // it to complete before we can know if it's the end of
              // the sequence
              SomeClass item = await itemAsync;
              // End of sequence?
              if (item == null) 
                  break;
              Console.WriteLine(item);
          }
      }