问题
(Just a theoretical question - for non-gui apps)
Assuming I have this code with many awaits
:
public async Task<T> ConsumeAsync()
{
await A();
await b();
await c();
await d();
//..
}
Where each task can take a very short period of time ,
Question (again , theoretical)
There could be a situation where the overall time dealing with all those "releasing back threads" and "fetching threads back" ( red & green here :)
Is taking more time than a single thread which could done all the work with a small amount of delay ,
I mean , I wanted to be the most productive , but instead , since all those switches back and forth - I actually lost productivity.
Can such scenario occur ?
回答1:
A Task
object represent the deferred result of a pending operation. You don't have to use tasks and async/await
if you don't have any pending operations. Otherwise, I believe async
/await
code is generally more efficient than its bare TPL ContinueWith
analogue.
Let's do some timing:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
// async/await version
static async Task<int> Test1Async(Task<int> task)
{
return await task;
}
// TPL version
static Task<int> Test2Async(Task<int> task)
{
return task.ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
static void Tester(string name, Func<Task<int>, Task<int>> func)
{
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 10000000; i++)
{
func(Task.FromResult(0)).Wait();
}
sw.Stop();
Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}
static void Main(string[] args)
{
Tester("Test1Async", Test1Async);
Tester("Test2Async", Test2Async);
}
}
}
The output:
Test1Async: 1582ms Test2Async: 4975ms
So, by default, await
continuations are handled more efficiently than ContinueWith
continuations. Let's optimize this code slightly:
// async/await version
static async Task<int> Test1Async(Task<int> task)
{
if (task.IsCompleted)
return task.Result;
return await task;
}
// TPL version
static Task<int> Test2Async(Task<int> task)
{
if (task.IsCompleted)
return Task.FromResult(task.Result);
return task.ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
The output:
Test1Async: 1557ms Test2Async: 429ms
Now the non-async version wins. In case with the async
version, I believe this optimization has already been done internally by the async/await
infrastructure.
Anyway, so far we've dealt only with completed tasks (Task.FromResult
). Let's introduce the actual asynchrony (naturally, we'll do less iterations this time):
static Task<int> DoAsync()
{
var tcs = new TaskCompletionSource<int>();
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(0));
return tcs.Task;
}
static void Tester(string name, Func<Task<int>, Task<int>> func)
{
ThreadPool.SetMinThreads(200, 200);
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
func(DoAsync()).Wait();
}
sw.Stop();
Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}
The output:
Test1Async: 4207ms Test2Async: 4734ms
Now the difference is very marginal, although the async
version still performs slightly better. Yet I think such gain is really neglectable, comparable to the actual cost of the asynchronous operation or to the cost of restoring the captured context for when SynchronizationContext.Current != null
.
The bottom line is, if you deal with asynchronous tasks, go for async
/await
if you have a choice, not for performance reason but for ease of use, readability and maintainability.
回答2:
Yes, in theory. Not normally, in the real world.
In the common case, async
is used for I/O-bound operations, and the overhead of thread management is undetectable in comparison to them. Most of the time, asynchronous operations either take a very long time (compared to thread management) or are already completed (e.g., a cache). Note that async
has a "fast path" that kicks in if the operation is already completed, where it does not yield the thread.
For more information, see the Zen of Async and Async Performance.
回答3:
Yes, it can happen. Also do not forget that - all efficiency in there that you can program - the task system DOES have overhead.
If you get too granulkar with something like this, the synchronization overhead CAN kill you. THAT SAID: Tasks are quite efficienttly programmed.
But the old rule sticks: do not go super granular. SOmetimes optimization helps.
回答4:
Can such scenario occur?
Absolutely. For this reason, you should be conscientious about where you use async code. Typically you're best using it for methods that will actually perform an asynchronous operation (disk or network I/O, for example). The time that these operations take usually far outweighs the cost of scheduling tasks on threads. Also, at the operating system level, these sorts of operations are inherently asynchronous, so you're actually removing a layer of abstraction by using async methods.
Even in these cases, though, you will likely not see a noticeable performance difference by switching to asynchronous code unless you are able to take advantage of concurrency. For example, the code you posted would probably see no real performance gain unless it were changed to something like this:
await Task.WhenAll(new[]{A(), B(), C(), D(), ...});
回答5:
Yes, of course it can happen. With all the overhead of the creating the state machine, yielding back and forth the control and using IOCP
threads. But as said, the TPL
is pretty optimized. For example, lets not forget that if your TaskAwaitable
finishes quickly, there might be no overhead and it will execute synchronously, which might happen often with quick operations.
来源:https://stackoverflow.com/questions/23871806/async-await-performance