我想知道await
- Task<int> calculateMillionthPrimeNumber = CalculateMillionthPrimeNumberAsync();
DoIndependentWork();
int p = await calculateMillionthPrimeNumber;
是否应该用于“高CPU”任务。我在演讲中看到了这一点。
所以我猜这就像是
class Show {
public:
void show_all(int num, int id_master, int id_slave, int periferia, int data);
};
我的问题是 可能 以上是否合理,或者如果没有,还有其他一些使高CPU任务异步的示例吗?
答案 0 :(得分:9)
事实上,async / await有两个主要用途。一个(我的理解是,这是它被放入框架的主要原因之一)是使调用线程在等待结果时执行其他工作。这主要用于I / O绑定任务(即主要&#34;保持&#34;是某种I / O的任务 - 等待硬盘驱动器,服务器,打印机等响应或完成其任务)。
作为旁注,如果您以这种方式使用async / await,确保您以调用线程实际可以执行的方式实现它是非常重要的等待结果的其他工作;我已经看到很多人在做人们做类似事情的事情;等待B等待C&#34;这可能最终表现不会比A同时调用B同步而B同步调用C(因为调用线程在等待B和C的结果时从不允许做其他工作)
在I / O绑定任务的情况下,创建额外线程只是等待结果的重点。我在这里通常的比喻是考虑在一个有10人的餐馆中订购。如果服务员要求订购的第一个人还没有准备好,那么服务员在接受其他任何人的订单之前并不等他准备好了,他也没有带第二个服务员只是等待第一个人。在这种情况下,最好的办法是询问小组中的其他9人的订单;希望,在他们订购的时候,第一个人就会准备好。如果没有,至少服务员还能节省一些时间,因为他花的时间更少。
也可以使用Task.Run
之类的东西来执行CPU绑定任务(这是第二次使用它)。按照上面的类比,这种情况下,有更多的服务员通常是有用的 - 例如如果一个服务员有太多的桌子供服务。真的,所有这一切实际上都是&#34;在幕后&#34;是使用线程池;它是进行CPU绑定工作的几种可能结构之一(例如,直接将它放在线程池上,显式创建新线程,或使用Background Worker)它是一个设计问题,你最终使用哪种机制。
async/await
的一个优点是它可以(在适当的情况下)减少必须手动编写的显式锁定/同步逻辑的数量。这是一个愚蠢的例子:
private static async Task SomeCPUBoundTask()
{
// Insert actual CPU-bound task here using Task.Run
await Task.Delay(100);
}
public static async Task QueueCPUBoundTasks()
{
List<Task> tasks = new List<Task>();
// Queue up however many CPU-bound tasks you want
for (int i = 0; i < 10; i++)
{
// We could just call Task.Run(...) directly here
Task task = SomeCPUBoundTask();
tasks.Add(task);
}
// Wait for all of them to complete
// Note that I don't have to write any explicit locking logic here,
// I just tell the framework to wait for all of them to complete
await Task.WhenAll(tasks);
}
显然,我在这里假设任务完全可以并行化。另请注意,您可能在这里自己使用了线程池,但这样做会不那么方便,因为您需要某种方法来弄清楚自己是否所有这些都已完成(而不仅仅是让框架为你解决这个问题)。您也可以在此处使用Parallel.For
循环。
答案 1 :(得分:7)
我想知道async-await是否应该用于“高CPU”任务。
是的,那是真的。
我的问题是以上是否合理
我会说这是不合理的。在一般情况下,您应该避免使用Task.Run
来实现具有异步签名的方法。 Don't expose asynchronous wrappers for synchronous methods。这是为了防止消费者混淆,特别是在ASP.NET上。
但是,使用Task.Run
调用同步方法(例如,在UI应用中)没有任何问题。通过这种方式,您可以使用多线程(Task.Run
)来保持UI线程的自由,并使用await
优雅地使用它:
var task = Task.Run(() => CalculateMillionthPrimeNumber());
DoIndependentWork();
var prime = await task;
答案 2 :(得分:4)
让我们说你的CalculateMillionthPrimeNumber
类似于以下内容(使用goto
时不是非常有效或理想,但很容易):
public int CalculateMillionthPrimeNumber()
{
List<int> primes = new List<int>(1000000){2};
int num = 3;
while(primes.Count < 1000000)
{
foreach(int div in primes)
{
if ((num / div) * div == num)
goto next;
}
primes.Add(num);
next:
++num;
}
return primes.Last();
}
现在,这里没有用处,可以异步执行某些操作。让我们使用async
:
public async Task<int> CalculateMillionthPrimeNumberAsync()
{
List<int> primes = new List<int>(1000000){2};
int num = 3;
while(primes.Count < 1000000)
{
foreach(int div in primes)
{
if ((num / div) * div == num)
goto next;
}
primes.Add(num);
next:
++num;
}
return primes.Last();
}
编译器会警告我们,因为await
任何有用的东西都无处可去。真正称之为与调用Task.FromResult(CalculateMillionthPrimeNumber())
稍微复杂一点的版本相同。也就是说,它与进行计算相同,然后创建一个已完成的任务,其中包含计算出的数字作为结果。
现在,已经完成的任务并不总是毫无意义。例如,考虑:
public async Task<string> GetInterestingStringAsync()
{
if (_cachedInterestingString == null)
_cachedInterestingString = await GetStringFromWeb();
return _cachedInterestingString;
}
当字符串在缓存中时,返回已完成的任务,否则返回,在这种情况下,它将返回非常快。其他情况是,如果同一接口有多个实现,并且并非所有实现都可以使用异步I / O.
同样是async
方法,await
此方法将返回已完成的任务或不依赖于此。它实际上是一种非常好的方式,可以保持同一个线程,并在可能的情况下完成所需的工作。
但是,如果总是可能,那么唯一的影响是创建Task
对象和async
使用的状态机的额外一点点膨胀实施它。
所以,毫无意义。如果这是您的问题中的版本实现的方式,则calculateMillionthPrimeNumber
从一开始就会IsCompleted
返回true。您应该刚刚调用了非异步版本。
好的,作为CalculateMillionthPrimeNumberAsync()
的实施者,我们想要为用户做一些更有用的事情。所以我们这样做:
public Task<int> CalculateMillionthPrimeNumberAsync()
{
return Task.Factory.StartNew(CalculateMillionthPrimeNumber, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}
好的,现在我们不会浪费用户的时间。 DoIndependentWork()
将在CalculateMillionthPrimeNumberAsync()
的同时执行操作,如果它首先完成,则await
将释放该线程。
大!
只是,我们还没有真正从同步位置移动针。实际上,特别是如果DoIndependentWork()
不是很艰难,我们可能会使情况变得更糟。同步方式将在一个线程上执行所有操作,我们称之为Thread A
。新方法在Thread B
上进行计算,然后释放Thread A
,然后以几种可能的方式进行同步。它有很多工作,它有什么收获吗?
好吧也许,但CalculateMillionthPrimeNumberAsync()
的作者无法知道,因为影响它的因素都在调用代码中。调用代码本身可以完成StartNew
,并且能够更好地满足同步选项的需要。
因此,虽然任务可以是一种与另一个任务并行调用cpu绑定代码的便捷方式,但这样做的方法并不实用。更糟糕的是,当有人看到CalculateMillionthPrimeNumberAsync
时,他们会被欺骗,因为他们相信这种做法并不是毫无意义。
答案 3 :(得分:1)
除非CalculateMillionthPrimeNumberAsync
一直使用async/await
,否则没有理由不让Task运行繁重的CPU工作,因为它只是将你的方法委托给ThreadPool的线程。
什么是ThreadPool线程,它与常规线程的不同之处是here。
简而言之,它只需要将线程池线程保留一段时间(并且线程池线程的数量有限),因此,除非你占用太多它们,否则没有什么可担心的。