【发布时间】:2020-09-04 07:37:19
【问题描述】:
我想了解异步在 c# 中的工作原理,并找到了一些基于 Task.Delay 的基本示例。它们工作正常,这样的代码可以按预期工作:
static int taskNo = 0;
static async Task<Task> test(int k)
{
int _taskNo = ++taskNo;
Console.WriteLine($"Started task {_taskNo}");
await Task.Delay(1000 * k);
Console.WriteLine($"Finished task {_taskNo}");
return Task.CompletedTask;
}
static async Task Main(string[] args)
{
Task[] tasks = new Task[3];
tasks[0] = test(4);
tasks[1] = test(3);
tasks[2] = test(2);
await Task.WhenAll(tasks);
Console.WriteLine("All tasks finished.");
Console.ReadLine();
}
最初我想尝试制作一些大循环,例如添加数千次字符串,只是为了尝试一下。是否可以异步执行?当我尝试使用这样的代码时,它只能在一个线程上工作(我可以看到只有一个线程在 100% 工作)。如何创建该循环的多个实例以在 CPU 的每个线程上工作?
//how to make this code to be able to work asynchronously?
static async Task LoopTest()
{
int _taskNo = ++taskNo;
Console.WriteLine($"Started task {_taskNo}");
string s = "";
for(int i = 0; i < 1000000; i++)
{
s += "qwertyuzxcvb";
}
Console.WriteLine($"Finished task {_taskNo}");
}
编辑: 在其中一个 cmets 中,我询问了一个线程如何比多个线程更快地执行操作,这是我测试的代码:
static int taskNo = 0;
static async Task LoopTest()
{
int _taskNo = ++taskNo;
Console.WriteLine($"Started task {_taskNo}");
string s = "";
for(int i = 0; i < 30000; i++)
{
s += "qwertyuzxcvb";
}
Console.WriteLine($"Finished task {_taskNo}");
}
static async Task Main(string[] args)
{
DateTime asyncStartTime = DateTime.Now;
Console.WriteLine($"Started async.");
// async
Task[] tasks = new Task[15];
Parallel.ForEach(tasks, (Task) => LoopTest());
DateTime asyncEndTime = DateTime.Now;
taskNo = 0;
//sync
DateTime syncStartTime = DateTime.Now;
Console.WriteLine($"Started sync.");
for(int i = 0; i < tasks.Length; i++)
{
LoopTest();
}
DateTime syncEndTime = DateTime.Now;
TimeSpan asyncTimeSpan = asyncEndTime - asyncStartTime;
TimeSpan syncTimeSpan = syncEndTime - syncStartTime;
Console.WriteLine($"Async finished in {asyncTimeSpan}, sync finished in {syncTimeSpan}.");
Console.ReadLine();
}
我可以看到,当异步启动时,所有线程都被固定到 100%(这是 i7 9750H,12 个线程正在工作,它说 CPU 使用率为 100%),一旦它开始同步,只有一个线程被固定到100%,整体 CPU 使用率为 25%。也许只是有一些愚蠢的错误,但是如何才能更快地同步呢?
顺便说一句,我知道这是一个非常愚蠢的异步示例,而且我知道在其他情况下有很多好处,但我只是想做那个循环测试,现在我有越来越多的问题......
【问题讨论】:
-
为了您的测试目的,您应该使用
Task.Run()来开始新任务。 -
Task.Run(() => my_hard_work_method()); -
您使用错误的示例来查看异步的好处。对于 splittin 计算操作,请使用
Parallel.ForEach,这将消耗多个处理器内核。要同时访问外部资源(数据库、文件系统、Web 服务或其他),请使用异步方法。 IO 操作的异步方法将只使用一个线程,但它同时等待来自不同资源的响应。 -
@Colinovsky 我测试了你的代码,结果是
Async finished in 00:00:00.4990435, sync finished in 00:00:00.5686410.。所以正如预期的那样,parallel 版本要快一些。请注意,在您的代码中 nothing 是异步的,这是因为您没有使用任何 true 异步方法并等待它的任务。但也请注意,在一般情况下 async/await 并不是并行化代码的正确工具。您可能需要在输出中添加on {Thread.CurrentThread.ManagedThreadId},以查看在哪个线程上执行了LoopTest调用。 -
术语异步在这里不适用。您可能希望实现并行性。您可能需要检查this 问题,以解决问题。
标签: c# .net loops asynchronous async-await