方法返回在 C# 控制台应用程序中同步运行的任务
Method returning task behaving synchronously in C# console app
在 C# 控制台应用程序中,我有一个带有几个异步方法的存储库 class:
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
的内部结构,因为我意识到它们可能是问题的罪魁祸首。
Is it because GetAllFooAsync is actually synchronous, but imitating an asynchronous workflow?
是的,Task.FromResult
returns 任务立即 RanToCompletion
所以它是同步的。人们经常忘记,在某些情况下,任务在返回时可能已经完成,因此不会 运行 异步。
async
关键字的存在不会使方法异步,它只是向编译器发出信号,将方法的代码转换为状态机 class,该状态机已准备好与异步流一起使用。实际上,一个方法在执行 I/O 等异步操作时变为异步方法,将工作卸载到另一个线程等,在这种情况下,它由 3 部分组成:异步操作之前的同步部分,异步调用启动操作的操作和 returns 对调用线程的控制,以及一个延续。在您的情况下,最后两部分不存在,因此您的调用是同步的。
您已经得到了一些很好的答案来描述 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);
}
如前所述,这是一个同步方法,但它有一个异步签名。这令人困惑;如果该方法不是异步的,那么使用同步的 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);
}
所以现在我们有了同步 APIs 的同步实现。现在的问题是关于消费。如果你从 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 上)。
在 C# 控制台应用程序中,我有一个带有几个异步方法的存储库 class:
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
的内部结构,因为我意识到它们可能是问题的罪魁祸首。
Is it because GetAllFooAsync is actually synchronous, but imitating an asynchronous workflow?
是的,Task.FromResult
returns 任务立即 RanToCompletion
所以它是同步的。人们经常忘记,在某些情况下,任务在返回时可能已经完成,因此不会 运行 异步。
async
关键字的存在不会使方法异步,它只是向编译器发出信号,将方法的代码转换为状态机 class,该状态机已准备好与异步流一起使用。实际上,一个方法在执行 I/O 等异步操作时变为异步方法,将工作卸载到另一个线程等,在这种情况下,它由 3 部分组成:异步操作之前的同步部分,异步调用启动操作的操作和 returns 对调用线程的控制,以及一个延续。在您的情况下,最后两部分不存在,因此您的调用是同步的。
您已经得到了一些很好的答案来描述 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);
}
如前所述,这是一个同步方法,但它有一个异步签名。这令人困惑;如果该方法不是异步的,那么使用同步的 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);
}
所以现在我们有了同步 APIs 的同步实现。现在的问题是关于消费。如果你从 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 上)。