如果我想实现这个线程概念,我应该如何使用TPL?

时间:2017-07-17 16:00:49

标签: c# .net multithreading asynchronous async-await

我们说我有一个类似SaveAsync(Item item)的方法,我需要在10 Item上调用它,并且这些调用是相互独立的。我认为在线程方面理想的方式是,如

Thread A | Run `SaveAsync(item1)` until we hit the `await` | ---- ... ---- | Run `SaveAsync(item10)` until we hit the `await` | ---------------------------------------|
Thread B | --------------------------------------------------- | Run the stuff after the `await` in `SaveAsync(item1)` | ------------------ ... -----------------------|  
Thread C | ------------------------------------------------------ | Run the stuff after the `await` in `SaveAsync(item2)` | ------------------ ... --------------------|  
.
.
.                                                                 

(对于多个项目的await之后的某些内容可能在同一个线程中运行,甚至是线程A)

我想知道如何在C#中编写它?它是一个平行的foreach还是一个等待SaveAsync(item)或者什么的循环?

2 个答案:

答案 0 :(得分:2)

每个默认async任务将始终返回到启动它们的线程上下文。您可以通过添加

来更改此设置
await task.ConfigureAwait(false)

这允许告诉运行时你不关心任务将恢复哪个线程上下文,运行时可以省略当前线程上下文的捕获(这是非常昂贵的)。

但是,默认情况下,您将始终在启动任务的线程上下文中进行调度。

默认上下文较少,例如ui线程上下文或线程池上下文。在ui线程上下文上启动的任务将被安排回ui线程上下文。 在线程池上下文中启动的任务将被安排到池中的下一个空闲线程。不一定是启动任务的相同线程。

但是,如果您需要对任务计划进行更多控制,则可以提供自己的上下文。

如何以如上所述的方式启动多个任务。循环在这里没有帮助。让我们举个例子。

foreach(var item in items)
{
    await SaveAsync(item);
}

await此处将等到SaveAsync完成。因此,所有保存都按顺序处理。

如何真正保存异步?

诀窍是启动所有任务,但不等待它们,直到所有任务都启动。然后使用wait WhenAll(IEnumerable<Task>) var tasks = new List<Task>(); foreach(var item in items) { tasks.Add(SaveAsync(item)); // No await here } await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed) 完成所有任务。

这是一个例子。

Save

由于缺少等待,所有&#34;保存动作&#34;放在Async / Await状态机中。一旦第一个任务返回,第二个任务就会被执行。这将导致某种行为与您的问题中描述的行为有些类似。

这里唯一的主要区别是所有任务都在同一个线程中执行。这大部分时间都是完整的,因为所有Task.Run(SaveAsync(item)); 方法通常都需要访问相同的资源。并行化它们并没有真正的优势,因为瓶颈就是这种资源。

如何使用多个线程

您可以使用

在新线程上执行任务
var tasks = new List<Task>();
foreach(var item in items)
{
    tasks.Add(Task.Run(SaveAsync(item));); // No await here
}

await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)

这将在从线程池中获取的新线程上执行线程,但是没有等待启动新线程并在ui线程上完成该方法。

要执行不同线程上的所有项目,您可以使用与以前几乎相同的代码:

StartNew

唯一的区别在于,我们将结果从Task.Run返回。

一句话:使用ERROR in *path*/tsconfig.json error TS2688: Cannot find type definition file for 'mocha'. 并不能保证您有新的帖子。它将在线程池中的下一个空闲线程上执行任务。这取决于您的本地设置以及本地配置(例如,繁重的准系统服务器将拥有比任何消费者笔记本电脑更多的线程)。

是否获得新线程或者您必须等待任何已占用的线程完成完全取决于线程池。 (胎面池通常做得非常好。有关更多信息,这里有关于线程池性能的非常好的文章:CLR-Thread-Pool

答案 1 :(得分:1)

这是人们在async / await中犯下大部分错误的地方:

1)要么人们认为,在调用异步方法之后,有/无等待的所有内容都会转换为ThreadPool线程。

2)或者人们认为异步确实同步运行。

真相介于两者之间,@ Iqon关于下一段代码的陈述实际上是错误的:“这里唯一的主要区别是,所有任务都在同一个线程中执行。”

var tasks = new List<Task>();
foreach(var item in items)
{
    tasks.Add(SaveAsync(item)); // No await here
}

await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)

要使这样的语句表明异步方法SaveAsync(item)实际上能够完全同步地执行。

以下是示例:

async Task SaveAsync1(Item item)
{
    //no awaiting at all
}   

async Task SaveAsync2(Item item)
{
    //awaiting already completed task
    int i = await Task.FromResult(0);
}   

这些方法实际上会在执行异步任务的线程上同步运行。但这些异步方法是特殊的雪花。这里没有等待的操作,即使等待在方法内部,一切都已完成,因为它不等待第一种情况并且在第二种情况下等待已完成的任务,所以它在等待后同步继续并且这两个调用将是相同的:

var taskA = SaveAsync2(item);//it would return task runned to completion

//same here, await wont happen as returned task was runned to completion
await SaveAsync2(item);

因此,制作语句,在这里同步执行异步方法只在这种特殊情况下是正确的:

var tasks = new List<Task>();
foreach(var item in items)
{
    tasks.Add(SaveAsync2(item));
}

await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)

并且不需要存储任务并等待Task.WhenAll(任务),这一切都已经完成,这就足够了:

foreach(var item in items)
{
    SaveAsync2(item);
    //it will execute synchronously because there is
    //nothing to await for in the method
}

现在让我们探索一下真实案例,一种异步方法,可以在内部等待某些内容或激发等待操作:

async Task SaveAsync3(Item item)
{
    //awaiting already completed task
    int i = await Task.FromResult(0);

    await Task.Delay(1000);
    Console.WriteLine(i);
}

现在这会做什么?

var tasks = new List<Task>();
foreach(var item in items)
{
    tasks.Add(SaveAsync3(item));
}

await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)

会同步运行吗?不!

它会并行运行吗? NO!

正如我在开始时说的那样,事实是介于异步方法之间,除非它们是像SaveAsync1和SaveAsync2这样的特殊雪花。

那么代码做了什么?它同步执行每个SaveAsync3直到等待Task.Delay,它发现返回的任务不完整并返回给调用者并提供了存储到任务的未完成任务,然后以同样的方式执行了SaveAsync。

现在等待Task.WhenAll(任务);具有真正的意义,因为它正在等待一些不完整的操作,它将在这个线程上下文之外并行运行。 在等待Task.Delay 之后,SaveAsync3方法的所有部分将被调度到ThreadPool并将并行运行,除非特殊情况如UI线程上下文,在这种情况下需要在TaskDelay之后的ConfigureAwait(false)。

希望你们明白,我想说什么。除非您有关于它或代码的更多信息,否则您无法真正说出异步方法的运行方式。

此练习还会打开问题,何时在异步方法上执行Task.Run。 它经常被误用,我认为,实际上只有两个主要案例:

1)当你想要从当前线程上下文中断时,比如UI,ASP.NET等

2)当异步方法具有同步部分(直到第一个未完成等待)时,这是计算密集型的,并且您想要卸载它,而不仅仅是不完整的等待部分。如果SaveAsync3将长时间计算变量 i ,那么就说斐波那契:)。

例如,你不必在类似SaveAsync的东西上使用Task.Run,​​这会打开文件并异步保存,除非在第一次等待SaveAsync内部之前的同步部分是一个问题,需要时间。然后Task.Run按顺序排列,如案例2)。