在C#控制台应用程序中同步返回行为的方法的方法

时间:2019-04-15 11:39:40

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

在C#控制台应用程序中,我有一个带有几个异步方法的repo类:

public class SomeRepo
{
   internal Task<IList<Foo>> GetAllFooAsync() 
   { 
       // this is actually fake-async due to legacy code.
       var result = SomeSyncMethod();
       return Task.FromResult(result);
   }

   public Task<IList<Foo>> GetFilteredFooAsync()
   {             
       var allFoos = await GetAllFooAsync().ConfigureAwait(false);
       return allFoos.Where(x => x.IsFiltered);
   }
}

Program.cs中:

var someRepo = new SomeRepo();
var filteredFoos = someRepo.GetFilteredFooAsync(); // no await
// a couple of additional async calls (to other classes) without await..
// .. followed by:
await Task.WhenAll(filteredFoos, otherTask, anotherTask).ConfigureAwait(false);

让我感到困惑的是,如果我在Program.cs的第二行上放置一个断点,则对someRepo.GetFilteredFooAsync()的调用不会继续到下一行,而是会停留在操作完成之前完成(好像是同步的)。而如果我将呼叫更改为GetAllFooAsync(在GetFilteredFooAsync中)以包装在Task.Run中:

public class SomeRepo
{
   internal Task<IList<Foo>> GetAllFooAsync() { // ... }

   public Task<IList<Foo>> GetFilteredFooAsync()
   {             
       var allFoos = await Task.Run(() => GetAllFooAsync).ConfigureAwait(false);
       return allFoos.Where(x => x.IsFiltered);
   }
}

..此操作按预期方式工作。是因为GetAllFooAsync实际上是同步的,却模仿了异步工作流吗?

编辑:重新命名标题并添加了GetAllFooAsync的内部信息,因为我意识到它们可能是问题的元凶。

3 个答案:

答案 0 :(得分:2)

  

是因为GetAllFooAsync实际上是同步的,但却模仿了异步工作流吗?

是的,Task.FromResult返回一个紧接RanToCompletion的任务,因此它是同步的。人们常常会忘记,在某些情况下,任务返回时可能已经完成,因此不会异步运行。

  

This method creates a Task object whose Task.Result property is result and whose Status property is RanToCompletion. The method is commonly used when the return value of a task is immediately known without executing a longer code path. The example provides an illustration.

答案 1 :(得分:2)

存在async关键字并不会使方法异步,它只是通知编译器将方法的代码转换为状态机类,该类准备与异步流一起使用。实际上,方法在执行异步操作(例如I / O,将工作分流到另一个线程等)时变为异步,并且在这种情况下,它由3部分组成:异步操作之前的同步部分,异步操作的调用,启动操作,并将控制权返回给调用线程,并继续执行。在您的情况下,后两个部分不存在,因此您的通话是同步的。

答案 2 :(得分:2)

您已经获得了描述how async doesn't make things asynchronous, how async methods begin executing synchronously, and how await can act synchronously if its task is already completed的好答案。

那么,让我们来谈谈设计。

internal Task<IList<Foo>> GetAllFooAsync() 
{ 
  // this is actually fake-async due to legacy code.
  var result = SomeSyncMethod();
  return Task.FromResult(result);
}

如前所述,这是一个 synchronous 方法,但是它具有异步签名。这令人困惑;如果该方法不是异步的,则最好使用同步API:

internal IList<Foo> GetAllFoo()
{ 
  // this is actually fake-async due to legacy code.
  var result = SomeSyncMethod();
  return result;
}

以及类似的调用方法:

public IList<Foo> GetFilteredFoo()
{             
  var allFoos = GetAllFoo();
  return allFoos.Where(x => x.IsFiltered);
}

因此,现在我们有了使用同步API的同步实现。现在的问题是关于消费。如果您从ASP.NET使用此功能,则建议同步使用它们。但是,如果要从GUI应用程序中使用它们,则可以使用Task.Run将同步工作卸载到线程池线程中,然后将其视为异步:

var someRepo = new SomeRepo();
var filteredFoos = Task.Run(() => someRepo.GetFilteredFoo()); // no await
// a couple of additional async calls (to other classes) without await..
// .. followed by:
await Task.WhenAll(filteredFoos, otherTask, anotherTask).ConfigureAwait(false);

具体来说,我建议不要使用Task.Run来实现,例如GetAllFooAsyncYou should use Task.Run to call methods, not to implement themTask.Run应该主要用于从GUI线程(即,不在ASP.NET上)调用同步代码。