英文:
Async methods run synchronously
问题
我有以下的代码:
public class SomeClass{
public async Task DoSomething(){
Task<Result1> result1Task = GetResult1Async();
Task<Result2> result2Task = GetResult2Async();
await Task.WhenAll(result1Task , result2Task);
// 继续的代码 ..
}
private Task<Result1> GetResult1Async(){
return _context.Result1.ToListAsync();
}
private Task<Result2> GetResult2Async(){
return _context.Result2.ToListAsync();
}
}
调试或运行这段代码时,调试器会等待每个任务单独完成,而不是记住任务并仅在 Task.WhenAll
行等待。是否有什么问题,或者如何使它异步运行?
英文:
I have the following code:
public class SomeClass{
public async Task DoSomething(){
Task<Result1> result1Task = GetResult1Async();
Task<Result2> result2Task = GetResult2Async();
await Task.WhenAll(result1Task , result2Task);
// continues code ..
}
private Task<Result1> GetResult1Async(){
return _context.Result1.ToListAsync();
}
private Task<Result2> GetResult2Async(){
return _context.Result2.ToListAsync();
}
}
Debugging or running this code the debugger waits until each task is finished seperately instead of remembering the task and only wait at row Task.WhenAll
. Is there anything wrong respectively how do I get that running async?
答案1
得分: 1
一般情况下,异步方法会非常快地创建一个不完整的 Task
,比这个 Task
完成所需的时间要快得多。这就是根据 Microsoft 的指南 异步方法应该表现的方式。但在实际生活中,一些异步 API 在创建 Task
期间执行了所有工作,花费了很多时间,并返回一个已经完成的任务 (task.IsCompleted == true
)。这不是一个良好的行为,但作为 API 的消费者,你不能太多干预。不良行为已经嵌入到 API 的实现中。你可以做的是将表现不佳的方法转移到 ThreadPool
,以便它们阻塞线程池线程而不是当前线程。最简单的方法是使用 Task.Run
方法:
public async Task DoSomething()
{
Task<Result1> result1Task = Task.Run(() => GetResult1Async());
Task<Result2> result2Task = Task.Run(() => GetResult2Async());
await Task.WhenAll(result1Task, result2Task);
// ...
}
Task.Run
在 ThreadPool
上调用异步 lambda,并返回一个代表调用完成和异步操作完成的代理任务。
如果你的应用程序是 ASP.NET,要注意使用 Task.Run
将工作转移到 ThreadPool
可能会影响应用程序的可伸缩性。
> 通过等待 Task.Run
来启动一些后台工作是没有意义的。事实上,这实际上会通过干扰 ASP.NET 线程池启发式算法来损害可伸缩性。如果在 ASP.NET 上有需要占用 CPU 的工作,最好的方式是直接在请求线程上执行它。一般规则是,在 ASP.NET 上不要将工作排队到线程池中。
顺便提一下,DbContext
不是线程安全的,不支持并发操作。如果你的目标是并发操作,你应该为每个操作实例化一个专用的 DbContext
。
英文:
Asynchronous methods in general create an incomplete Task
very fast, much faster than the time it takes for this Task
to complete. That's how asynchronous methods are supposed to behave according to Microsoft's guidelines. But in real life some asynchronous APIs are doing all the work during the creation of the Task
, taking a lot of time, and return an already completed task (task.IsCompleted == true
). This is not a good behavior, but as a consumer of the API you can't do much about it. The bad behavior is baked into the API's implementation. What you can do is to offload the misbehaving methods to the ThreadPool
, so that they block thread-pool threads instead of the current thread. The easiest way to do it is the Task.Run
method:
public async Task DoSomething()
{
Task<Result1> result1Task = Task.Run(() => GetResult1Async());
Task<Result2> result2Task = Task.Run(() => GetResult2Async());
await Task.WhenAll(result1Task, result2Task);
// ...
}
The Task.Run
invokes the asynchronous lambda on the ThreadPool
, and returns a proxy task that represents both the completion of the invocation, and the completion of the asynchronous operation.
In case your application is ASP.NET, be aware that offloading work to the ThreadPool
with Task.Run
might hurt the scalability of your application.
> You can kick off some background work by awaiting Task.Run
, but there’s no point in doing so. In fact, that will actually hurt your scalability by interfering with the ASP.NET thread pool heuristics. If you have CPU-bound work to do on ASP.NET, your best bet is to just execute it directly on the request thread. As a general rule, don’t queue work to the thread pool on ASP.NET.
As a side note, the DbContext
is not thread-safe, and it doesn't support concurrent operations. If you are aiming at concurrency, you should instantiate a dedicated DbContext
for each operation.
1: https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap#initiating-an-asynchronous-operation "Task-based asynchronous pattern in .NET - Initiating an asynchronous operation"
2: https://stackoverflow.com/questions/63217657/why-c-sharp-file-readalllinesasync-blocks-ui-thread/63218926#63218926 "Why File.ReadAllLinesAsync() blocks the UI thread?"
3: https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool
4: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run
5: https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/october/async-programming-introduction-to-async-await-on-asp-net#asynchronous-code-is-not-a-silver-bullet "Async Programming : Introduction to Async/Await on ASP.NET - Asynchronous Code Is Not a Silver Bullet"
答案2
得分: 1
不是所有的数据库提供程序都提供真正的异步调用。但实体框架的理念是使您的代码独立于数据库提供程序。因此,有时异步调用实际上是同步的。我知道至少一些较旧的MySQL提供程序存在这个问题。因此,请查看您的数据库提供程序的文档,或者只需更新数据库驱动程序。
代码结构方面也存在问题,如评论中所提到的。仅仅因为提供了异步API并不意味着API支持并发。
英文:
Not all database providers provide truly asynchronous calls. But the idea of entity framework is to make your code independent of database provider. As a result of this is that sometimes asynchronous calls are really synchronous. I know at least some older providers for MySQL has this problem. So check the documentation for your database provider, or just update the database driver.
There are also issues with the way the code is structured, as mentioned in the comments. Just because an async API is provided does not mean the API supports concurrency.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论