在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
的内部信息,因为我意识到它们可能是问题的元凶。
答案 0 :(得分:2)
是因为GetAllFooAsync实际上是同步的,但却模仿了异步工作流吗?
是的,Task.FromResult
返回一个紧接RanToCompletion
的任务,因此它是同步的。人们常常会忘记,在某些情况下,任务返回时可能已经完成,因此不会异步运行。
答案 1 :(得分:2)
存在async
关键字并不会使方法异步,它只是通知编译器将方法的代码转换为状态机类,该类准备与异步流一起使用。实际上,方法在执行异步操作(例如I / O,将工作分流到另一个线程等)时变为异步,并且在这种情况下,它由3部分组成:异步操作之前的同步部分,异步操作的调用,启动操作,并将控制权返回给调用线程,并继续执行。在您的情况下,后两个部分不存在,因此您的通话是同步的。
答案 2 :(得分:2)
那么,让我们来谈谈设计。
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
来实现,例如GetAllFooAsync
。 You should use Task.Run
to call methods, not to implement them和Task.Run
应该主要用于从GUI线程(即,不在ASP.NET上)调用同步代码。